Java高并发编程中ReentrantLock、ReentrantReadWriteLock的使用及详细介绍-刘宇
作者:刘宇
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