ReentrantLock 简介
ReentrantLock 实现了 Lock 接口,是一种可重入的独占锁。
相比于 synchronized 同步锁,ReentrantLock 更加灵活,拥有更加强大的功能,比如可以实现公平锁机制。
首先,先来了解一下什么是公平锁机制。
ReentrantLock 的公平锁机制
我们知道,ReentrantLock 分为公平锁和非公平锁,可以通过构造方法来指定具体类型:
//默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁
在多个线程竞争获取锁时,公平锁倾向于将访问权授予等待时间最长的线程。
也就是说,公平锁相当于有一个线程等待队列,先进入队列的线程会先获得锁,按照 "FIFO(先进先出)" 的原则,对于每一个等待线程都是公平的。
非公平锁
非公平锁是抢占模式,线程不会关注队列中是否存在其他线程,也不会遵守先来后到的原则,直接尝试获取锁。
接下来进入正题,一起分析下 ReentrantLock 的底层是如何实现的。
ReentrantLock 的底层实现
ReentrantLock 实现的前提是 AbstractQueuedSynchronizer(抽象队列同步器),简称 AQS,是 java.util.concurrent 的核心,常用的线程并发类 CountDownLatch、CyclicBarrier、Semaphore、ReentrantLock 等都包括了一个继承自 AQS 抽象类的内部类。
同步标志位 state
AQS 内部维护了一个同步标志位 state,用来实现同步加锁控制:
private volatile int state;
同步标志位 state 的初始值为 0,线程每加一次锁,state 就会加 1,也就是说,已经获得锁的线程再次加锁,state 值会再次加 1。可以看出,state 实际上表示的是已获得锁的线程进行加锁操作的次数。
CLH 队列
除了 state 同步标志位外,AQS 内部还使用一个 FIFO 的队列(也叫 CLH 队列)来表示排队等待锁的线程,当线程争抢锁失败后会封装成 Node 节点加入 CLH 队列中去。
Node 的代码实现:
static final class Node {
// 标识当前节点在共享模式
static final Node SHARED = new Node();
// 标识当前节点在独占模式
static final Node EX