本文代码基于Java8
前言
Reentrant 可重入的;重入;可再入的。即再次进入的意思,entrant: 新职员;新生;新会员;新成员。
JDK 中独占锁的实现除了使用关键字 synchronized
外,还可以使用 ReentrantLock
。虽然在性能上ReentrantLock
和 synchronized
没有什么区别,但 ReentrantLock
相比 synchronized
而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。即 ReentrantLock
粒度更小。
ReentrantLock
是独占锁且可重入的。ReentrantLock
可以实现公平锁。ReentrantLock
可以响应中断。ReentrantLock
提供了获取锁超时等待。ReentrantLock
结合Condition
接口可以实现线程间的等待通知机制。
ReentrantLock 类结构
1 | public class ReentrantLock implements Lock, java.io.Serializable { |
其中Sync
、FairSync
、NonfairSync
是 ReentrantLock
的内部类,Sync
继承自 AbstractQueuedSynchronizer
,而 FairSync
、NonfairSync
继承 Sync
,分别对应公平锁和非公平锁。
Sync 内部类
1 | abstract static class Sync extends AbstractQueuedSynchronizer { |
NonfairSync、FairSync 内部类
线程会重复获取锁。如果申请获取锁的线程足够多,那么可能会造成某些线程长时间得不到锁。这就是非公平锁的“饥饿”问题。
公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权,而非公平锁则随机分配这种使用权。和synchronized
一样,默认的 ReentrantLock
实现是非公平锁,因为相比公平锁,非公平锁性能更好。当然公平锁能防止饥饿,某些情况下也很有用。
1 | // 非公平锁 |
ReentrantLock 提供的方法
ReentrantLock 锁方法
ReentrantLock
和 synchronized
都是独占锁。不同的是 ReentrantLock
需要手动加锁和解锁,且解锁的操作尽量要放在 finally 代码块中,保证线程正确释放锁。ReentrantLock
操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。synchronized
加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用 synchronized
就足够。
ReentrantLock
和 synchronized
都是可重入的。synchronized
因为可重入,所以可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁。而 ReentrantLock
在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。
1 | // 加锁,分别调用公平锁和非公平锁的加锁方法 |
Condition 相关方法
Condition
由 ReentrantLock
对象创建,并且可以同时创建多个。Condition
接口在使用前必须先调用ReentrantLock
的 lock()
方法获得锁。之后调用 Condition
接口的 await()
将释放锁,并且在该Condition
上等待,直到有其他线程调用 Condition
的 signal()
方法唤醒线程。
1 | // 创建 Condition |
除了锁方法和 Condition 相关方法外,还有一些判断是否是公平锁、是否加锁、获取阻塞队列长度、当前队列是否独占等方法,比较简单参考源码即可。
ReentrantLock 使用示例
基于官方示例
1 | import java.util.concurrent.TimeUnit; |
如果是公平锁,线程几乎是轮流的获取到了锁。
1 | 获得锁的线程:1 |
如果是非公平锁,线程会重复获取锁。
1 | 获得锁的线程:4 |
结合 Condition 实现等待通知机制
使用 synchronized
结合 Object 上的 wait()
和 notify()
方法可以实现线程间的等待通知机制。ReentrantLock
结合 Condition
接口同样可以实现这个功能,而且相比前者使用起来更清晰也更简单。
使用Condition
实现简单的阻塞队列,如下:
1 | import java.util.LinkedList; |
总结
ReentrantLock
是可重入的独占锁。比 synchronized
功能更加丰富,控制粒度更小,支持公平锁实现,支持中断响应以及超时等待…… ReentrantLock
还可以配合一个或多个 Condition
条件方便的实现等待通知机制。不过需要手动加锁和解锁,且解锁的操作尽量要放在 finally 代码块中,保证线程正确释放。