Java高并发编程中ReentrantLock、ReentrantReadWriteLock的使用及详细介绍-刘宇

Java高并发编程中ReentrantLock、ReentrantReadWriteLock的使用及详细介绍-刘宇

  • 一、ReentrantLock的简单介绍
  • 二、ReentrantReadWriteLock的简单介绍
  • 三、Lock接口
    • 1、lock()
    • 2、lockInterruptibly()
    • 3、tryLock()
    • 4、unlock()
    • 5、newCondition()
  • 四、ReentrantLock的额外方法
    • 1、构造方法
    • 2、getOwner方法
    • 3、isHeldByCurrentThread方法
    • 4、isLocked方法
    • 5、isFair方法
    • 6、hasQueuedThreads方法
    • 7、hasQueuedThread方法
    • 8、getQueueLength方法
    • 9、getHoldCount方法
    • 10、getQueuedThreads方法
    • 11、hasWaiters方法
    • 12、getWaitQueueLength方法
  • 五、ReentrantReadWriteLock的方法介绍
    • 1、构造方法
    • 2、writeLock方法
    • 3、readLock方法
    • 4、isWriteLocked方法
    • 5、isWriteLockedByCurrentThread方法
  • 六、ReentrantLock的案例
    • 1、不可中断、可中断、立即返回获取锁的demo
    • 2、额外方法demo
  • 七、ReentrantReadWriteLock的案例
    • 1、读写案例
    • 2、读读案例

作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。

一、ReentrantLock的简单介绍

  • 可重入锁: 指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。synchronized 和 ReentrantLock 都是可重入锁。
  • ReentrantLock 实现Lock接口。

重入锁ReentrantLock 相对来说是synchronized、Object.wait()和Object.notify()方法的替代品(或者说是增强版),在JDK5.0的早期版本,重入锁的性能远远好于synchronized,但从JDK6.0开始,JDK在synchronized上做了大量的优化,使得两者的性能差距并不大。但ReentrantLock也有一些synchronized没法实现的特性。

二、ReentrantReadWriteLock的简单介绍

  • ReentrantReadWriteLock实现了ReadWriteLock接口。而ReadWriteLock里面的readLock()和writeLock()返回的实则是Lock接口对应的实现类。
  • 该读写锁实则拥有两个锁,一个是读锁,另一个是写锁。
  • 写锁只能被一个线程所占有,而读锁可以被多个线程占有。即:对于写操作,一次只有一个线程(write线程)可以修改共享数据,对于读操作,允许任意数量的线程同时进行读取。
  • 当有写锁被获取时,其他线程则不能获取到读锁。
  • 适用于在读多写少的情况下使用

好处:

  • 在写的时候加锁,在所有线程只有读操作的时候则就省去了加锁这个步骤,提高效率。

属性:

  • 重入:此锁允许reader和writer按照ReentrantLock的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入reader使用读取锁。
  • writer可以获取读取锁,但reader不能获取写入锁。
  • 锁降级:重入还允许从写入锁降级为读取锁,实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。
  • 锁获取的中断:读取锁和写入锁都支持锁获取期间的中断。
  • Condition支持:写入锁提供了一个Condition实现,对于写入锁来说,该实现的行为与ReentrantLock.newCondition()提供的Condition实现对ReentrantLock所做的行为相同。当然,此Condition只能用于写入锁。
  • 读取锁不支持 Condition,readLock().newCondition() 会抛出 UnsupportedOperationException。
  • 监测:此类支持一些确定是读取锁还是写入锁的方法。这些方法设计用于监视系统状态,而不是同步控制。

三、Lock接口

Lock是Java中锁的核心接口,提供一系列的基础函数

1、lock()

//获取锁,该方法会一直等到获取到锁为止,不可被打断
void lock();

2、lockInterruptibly()

//获取锁,可被打断
void lockInterruptibly() throws InterruptedException;

3、tryLock()

//立即返回结果,会尝试获取锁,如果拿到则返回true,反之false
boolean tryLock();
//在一定时间内尝试获取锁,如果拿到则返回true,反之false。可被中断
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

4、unlock()

//释放锁
boolean unlock();

5、newCondition()

注:关于Condition在我后面一篇的博客中有介绍到

//返回这个锁的Condition
Condition newCondition();

四、ReentrantLock的额外方法

1、构造方法

  • 默认不公平,不公平比公平的效率通常要高
  • fair:是否公平,如果公平则基本按照等待队列中的顺序去抢锁,反之不一定
ReentrantLock()
ReentrantLock(boolean fair)

2、getOwner方法

//获取当前被哪个线程获取
Thread getOwner()

3、isHeldByCurrentThread方法

//锁是否被当前线程获取到
boolean isHeldByCurrentThread()

4、isLocked方法

//判断锁是否被别的线程抢走
boolean isLocked()

5、isFair方法

//判断是否公平
boolean isFair()

6、hasQueuedThreads方法

//判断是否有线程处于等待中
boolean hasQueuedThreads()

7、hasQueuedThread方法

  • thread:需要判断的线程
//判断指定线程是否处于等待中
boolean hasQueuedThread(Thread thread)

8、getQueueLength方法

//获取有多少线程处于等待中
int getQueueLength()

9、getHoldCount方法

//获取调用该方法的线程获取锁的次数,因为可重入锁是可以再次获取该对象锁的
int getHoldCount()

10、getQueuedThreads方法

  • 该方法是protected修饰的,但是我们可以通过继承来获取到
//获取等待队列中的所有线程
Collection<Thread> getQueuedThreads()

11、hasWaiters方法

//根据Condition判断是否有等待线程,只能在当前线程被lock时候使用,否则就会抛出IllegalMonitorStateException异常
boolean hasWaiters(Condition condition)

12、getWaitQueueLength方法

//根据Condition获取等待队列的大小,只能在当前线程被lock时候使用,否则就会抛出IllegalMonitorStateException异常
int getWaitQueueLength(Condition condition)

五、ReentrantReadWriteLock的方法介绍

注:由于许多方法都和ReentrantLock中的方法类似,这边就简单介绍几个,其他请看源代码

1、构造方法

  • 默认不公平,不公平比公平的效率通常要高
  • fair:是否公平,如果公平则基本按照等待队列中的顺序去抢锁,反之不一定
ReentrantReadWriteLock()
ReentrantReadWriteLock(boolean fair)

2、writeLock方法

//返回写锁
ReentrantReadWriteLock.WriteLock writeLock()

3、readLock方法

//返回读锁
ReentrantReadWriteLock.ReadLock readLock()

4、isWriteLocked方法

//判断是否有线程拥有写锁
boolean isWriteLocked()

5、isWriteLockedByCurrentThread方法

//判断当前线程是否拥有写锁
boolean isWriteLockedByCurrentThread()

六、ReentrantLock的案例

1、不可中断、可中断、立即返回获取锁的demo

package com.brycen.concurrency03.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
	final static Lock lock = new ReentrantLock();
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(()->testUnInterruptReentrantLock());
		t1.start();
		TimeUnit.MILLISECONDS.sleep(100);
		
		Thread t2 = new Thread(()->testUnInterruptReentrantLock());
		t2.start();
		TimeUnit.MILLISECONDS.sleep(100);
		
		//测试lock()是否可以中断
		t2.interrupt();
		
		System.out.println("main finish");
	}
	//立即返回锁
	public static void testTryLock() {
		if(lock.tryLock()) {
			try {
				System.out.println(Thread.currentThread().getName()+" get lock and do some things");
				while(true) {
					
				}
			} finally {
				lock.unlock();
			}
		} else {
			System.out.println(Thread.currentThread().getName()+" not get lock and do some things");
		}
		
	}
	
	//可中断的获取锁
	public static void testInterruptReentrantLock() {
		try {
			//lockInterruptibly()是可中断的
			try {
				lock.lockInterruptibly();
				System.out.println(Thread.currentThread().getName()+" get lock and do some things");
				while(true) {
					
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} finally {
			lock.unlock();
		}
	}
	
	//不可中断的获取锁
	public static void testUnInterruptReentrantLock() {
		try {
			//lock()是不可中断的
			lock.lock();
			System.out.println(Thread.currentThread().getName()+" get lock and do some things");
			while(true) {
				
			}
		} finally {
			lock.unlock();
		}
	}
}

testUnInterruptReentrantLock的运行结果:

  • 程序一直在运行,因为写的while(true)
Thread-0 get lock and do some things
main finish

testInterruptReentrantLock的运行结果:

  • 程序一直在运行,因为写的while(true)
Thread-0 get lock and do some things
main finish
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.brycen.concurrency03.lock.ReentrantLockExample.testInterruptReentrantLock(ReentrantLockExample.java:46)
	at com.brycen.concurrency03.lock.ReentrantLockExample.lambda$1(ReentrantLockExample.java:15)
	at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at com.brycen.concurrency03.lock.ReentrantLockExample.testInterruptReentrantLock(ReentrantLockExample.java:55)
	at com.brycen.concurrency03.lock.ReentrantLockExample.lambda$1(ReentrantLockExample.java:15)
	at java.lang.Thread.run(Thread.java:745)

testTryLock的运行结果:

  • 程序一直在运行,因为写的while(true)
Thread-0 get lock and do some things
Thread-1 not get lock and do some things
main finish

2、额外方法demo

package com.brycen.concurrency03.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample2 {
	final static ReentrantLock reentrantLock = new ReentrantLock();
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(()->testReentrantLock());
		t1.start();
		TimeUnit.MILLISECONDS.sleep(100);
		
		Thread t2 = new Thread(()->testReentrantLock());
		t2.start();
		TimeUnit.MILLISECONDS.sleep(100);
		
		System.out.println("当前等待队列的线程数:"+reentrantLock.getQueueLength());
		System.out.println("是否有线程处于等待状态:"+reentrantLock.hasQueuedThreads());
		System.out.println("t1线程是否处于等待状态:"+reentrantLock.hasQueuedThread(t1));
		System.out.println("t2线程是否处于等待状态:"+reentrantLock.hasQueuedThread(t2));
		System.out.println("锁是否被其他线程获取到:"+reentrantLock.isLocked());
	}
	
	//额外方法测试
	public static void testReentrantLock() {
		try {
			reentrantLock.lock();
			//获取当前线程获取锁的次数
			System.out.println(Thread.currentThread().getName()+" 获取锁次数: "+reentrantLock.getHoldCount());
			System.out.println(Thread.currentThread().getName()+" get lock and do some things");
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			reentrantLock.unlock();
		}
	}
}

运行结果:

Thread-0 获取锁次数: 1
Thread-0 get lock and do some things
当前等待队列的线程数:1
是否有线程处于等待状态:true
t1线程是否处于等待状态:false
t2线程是否处于等待状态:true
锁是否被其他线程获取到:true
Thread-1 获取锁次数: 1
Thread-1 get lock and do some things

七、ReentrantReadWriteLock的案例

1、读写案例

package com.brycen.concurrency03.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockExample {
	final static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
	final static Lock writeLock = reentrantReadWriteLock.writeLock();
	final static Lock readLock = reentrantReadWriteLock.readLock();
	static List<Long> list = new ArrayList<Long>();
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(()->write());
		t1.start();
		//休眠100毫秒,确保线程t1先启动
		TimeUnit.MILLISECONDS.sleep(100);
		Thread t2 = new Thread(()->read());
		t2.start();
	}
	
	public static void write() {
		try {
			writeLock.lock();
			System.out.println(Thread.currentThread().getName()+" start writing");
			//休眠5秒模拟写操作
			TimeUnit.SECONDS.sleep(5);
			list.add(System.currentTimeMillis());
			System.out.println(Thread.currentThread().getName()+" finish writing");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			writeLock.unlock();
		}
	}
	public static void read() {
		try {
			readLock.lock();
			System.out.println(Thread.currentThread().getName()+" start reading");
			//休眠5秒模拟读操作
			TimeUnit.SECONDS.sleep(5);
			list.forEach((i)->{System.out.println(i);});
			System.out.println(Thread.currentThread().getName()+" finish reading");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			readLock.unlock();
		}
	}
}

运行结果:

  • 可以看出先要等线程t1写完之后,t2才能读
Thread-0 start writing
Thread-0 finish writing
Thread-1 start reading
1596550630927
Thread-1 finish reading

2、读读案例

package com.brycen.concurrency03.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockExample {
	final static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
	final static Lock writeLock = reentrantReadWriteLock.writeLock();
	final static Lock readLock = reentrantReadWriteLock.readLock();
	static List<Long> list = new ArrayList<Long>();
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(()->read());
		t1.start();
		//休眠100毫秒,确保线程t1先启动
		TimeUnit.MILLISECONDS.sleep(100);
		Thread t2 = new Thread(()->read());
		t2.start();
	}
	
	public static void write() {
		try {
			writeLock.lock();
			System.out.println(Thread.currentThread().getName()+" start writing");
			//休眠5秒模拟写操作
			TimeUnit.SECONDS.sleep(5);
			list.add(System.currentTimeMillis());
			System.out.println(Thread.currentThread().getName()+" finish writing");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			writeLock.unlock();
		}
	}
	public static void read() {
		try {
			readLock.lock();
			System.out.println(Thread.currentThread().getName()+" start reading");
			//休眠5秒模拟读操作
			TimeUnit.SECONDS.sleep(5);
			list.forEach((i)->{System.out.println(i);});
			System.out.println(Thread.currentThread().getName()+" finish reading");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			readLock.unlock();
		}
	}
}

运行结果:

  • t1线程和t2线程可以同时进行读取。
Thread-0 start reading
Thread-1 start reading
Thread-0 finish reading
Thread-1 finish reading

你可能感兴趣的:(Java)