Java并发编程--JUC并发工具类之ReentrantLock
摘要
-
本文介绍ReentrantLock相关技术
-
本文基于
jdk1.8
ReentrantLock
-
ReentrantLock 是一种可重入的独占锁,它允许同一个线程多次获取同一个锁而不会被阻塞。
-
它的功能类似于 synchronized,是一种互斥锁,可以保证线程安全。
-
相对于 synchronized,ReentrantLock 具备如下特点:
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
- 与 synchronized 一样,都支持可重入
-
它的主要应用场景是在多线程环境下对共享资源进行独占式访问,以保证数据的一致性和安全性。
常用API
方法 | 描述 |
---|---|
void lock() | 获取锁,调用该方法当前线程会获取锁,当锁获得后,该方法返回 |
void lockInterruptibly() throws InterruptedException | 可中断的获取锁,和lock() 方法不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程 |
boolean tryLock() | 尝试非阻塞的获取锁,调用该方法后立即返回。如果能够获取到返回true ,否则返回false |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException | 超时获取锁,当前线程在以下三种情况下会被返回: 1. 当前线程在超时时间内获取了锁,返回 true 2. 当前线程在超时时间内被中断,抛出 InterruptedException 3. 超时时间结束,返回 false |
void unlock() | 释放锁 |
Condition newCondition() | 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的await() 方法,而调用后,当前线程将释放锁 |
基本语法
1 | ReentrantLock lock = new ReentrantLock();//默认非公平锁 |
-
在使用时要注意 4 个问题:
-
- 默认情况下 ReentrantLock 为非公平锁而非公平锁;
-
- 加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常;
-
- 加锁操作一定要放在 try 代码之前,这样可以避免未加锁成功又释放锁的异常;
-
- 释放锁一定要放在 finally 中,否则会导致线程阻塞。
-
小贴士
公平锁和非公平锁
- 公平锁:线程在获取锁时,按照等待的先后顺序获取锁。
- 非公平锁:线程在获取锁时,不按照等待的先后顺序获取锁,而是随机获取锁。
可重入锁
-
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
-
Java中
ReentrantLock
和synchronized
都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。 -
在实际开发中,可重入锁常常应用于递归操作、调用同一个类中的其他方法、锁嵌套等场景中。
ReentrantLock的等待通知机制
-
java.util.concurrent
类库中提供Condition
类来实现线程之间的协调。 -
调用
Condition.await()
方法使线程等待,同时释放锁 -
其他线程调用
Condition.signal()
或Condition.signalAll()
方法唤醒等待的线程。 -
注意:调用
Condition
的await()
和signal()
方法,都必须在lock保护之内。 -
下面是一个使用ReentrantLock的生产者–消费者模型的示例代码,在这个示例程序中,我们使用了一个ReentrantLock和两个Condition(notFull和notEmpty)来实现等待通知机制。
1 | import java.util.LinkedList; |
-
对于
Condition
的await()
和signal()
方法,它们的行为与使用wait()
和notify()
方法时的情况类似。具体而言:Condition
的await()
方法会释放当前线程持有的锁,并使线程进入等待状态,直到接收到signal()
方法的通知后才会重新竞争锁并继续执行。Condition
的signal()
方法会发送一个通知给等待在该条件上的一个线程,使其从等待状态被唤醒。注意,signal()
方法执行后,并不会立即释放锁,它会等待当前线程执行完临界区代码后才会释放锁,然后等待被唤醒的线程重新竞争锁。
ReentrantLock具体应用场景
-
1.解决多线程竞争资源的问题,例如多个线程同时对同一个数据库进行写操作,可以使用ReentrantLock保证每次只有一个线程能够写入。
-
2.实现多线程任务的顺序执行,例如在一个线程执行完某个任务后,再让另一个线程执行任务。
-
3.实现多线程等待/通知机制,例如在某个线程执行完某个任务后,通知其他线程继续执行任务。
ReentrantLock的问题
-
ReentrantLock 是一个互斥锁,同一时间只允许一个线程持有锁,其他线程必须等待释放锁后才能获取锁,适用于那些读操作少、写操作多的场景,因为读操作时其他线程无法读取,导致并发性能较低。
-
ReentrantLock 可以是公平锁(fairness=true)或非公平锁(fairness=false)。在公平锁模式下,锁将按照线程请求的顺序分配,但会导致性能下降。在非公平锁模式下,线程有机会插队获取锁,可能导致某些线程长时间等待。