ReentrantLock
1. 前言
- ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。
- ReentrantLock分为“公平锁”和“非公平锁”。它们的区别体现在获取锁的机制上是否公平。
- ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁
- 最多支持同一线程的2147483647个递归锁,超过将抛出Error
ReentrantLock
和synchronized
区别:ReentrantLock
非阻塞,synchronized
阻塞ReentrantLock
获取锁时候可以中断,可以设置超时时间ReentrantLock
灵活性更高,允许获取多个锁,以不同顺序释放锁;允许在不同范围释放锁都具备线程重入特性;
ReentrantLock
表现为API
层面的互斥锁,synchronized
表现为原生语法层面的互斥锁ReentrantLock
具备以下高级功能:- 等待可中断
- 可实现公平锁:必须按照申请锁的时间顺序来依次获得锁
- 锁可以绑定多个条件
2. 源码解析
2.1 数据结构
- ReentrantLock与sync是组合关系。ReentrantLock中,包含了Sync对象,Sync是AQS的子类;
- Sync有两个子类FairSync(公平锁)和NonFairSync(非公平锁)。ReentrantLock是一个独占锁,它是公平锁还是非公平锁由Sync对象实例决定。
2.2函数列表
1 | public class ReentrantLock implements Lock, java.io.Serializable { |
2.3 公平锁和非公平锁
2.3.1 公平锁
- lock()
公平锁在FairSync类实现:
1 | final void lock() { |
acquire 在AQS中实现。
1 | public final void acquire(int arg) { |
过程如下:
- 首先通过
tryAcquire()
尝试获取锁,获取成功直接返回; - 当前线程
tryAcquire()
获取失败,通过addWaiter(Node.EXCLUSIVE)
方法将当前线程添加入CLH等待队列末尾 - 加入CLH队列后,通过
acquireQueued()
方法获取锁。“当前线程”在执行acquireQueued()
时,会进入到CLH队列中休眠等待,直到获取锁了才返回!如果“当前线程”在休眠等待过程中被中断过,acquireQueued()
会返回true,此时”当前线程”会调用selfInterrupt()来自己给自己产生一个中断
- tryAcquire方法在FairSync实现,尝试获取锁
1 | //尝试获取锁 |
1 | //CLH队列为空或者队首为当前线程返回false |
- addWaiter加入CLH等待队列
1 | //AQS中实现,加入等待队列尾节点 |
- acquireQueued获取锁
1 | //AQS中实现 |
1 | // 判断“当前线程是否应该阻塞” |
- unlock()
1 | public void unlock() { |
release释放锁在AQS中实现
1 | //尝试释放当前线程锁持有的锁。成功的话,则唤醒后继等待线程,并返回true。否则,直接返回false。 |
1 | //Sync中实现,尝试释放锁 |
1 | //唤醒当前线程的后继线程 |
2.3.2 非公平锁
非公平锁和公平锁主要在获取锁上有差别。
公平锁在尝试获取锁时,即使“锁”没有被任何线程锁持有,它也会判断自己是不是CLH等待队列的表头;是的话,才获取锁。
而非公平锁在尝试获取锁时,如果“锁”没有被任何线程持有,则不管它在CLH队列的何处,它都直接获取锁。
- lock()
1 | //NonfairSync类中实现,会先通过CAS直接获取锁;失败调用acquire(1)获取锁 |
- tryAcquire()
1 | protected final boolean tryAcquire(int acquires) { |
1 | //Sync类中实现,非公平尝试获取锁 |