阅读须知
- JDK版本:1.8
- 文章中使用/* */注释的方法会做深入分析
正文
ReentrantLock 从命令上理解为可重入锁,提供了比 synchronized 更加灵活的锁控制,ReentrantLock 基于 AQS(AbstractQueuedSynchronizer,不熟悉的读者可以查阅笔者关于 AQS 源码分析的相关文章进行学习,便于更好的理解本文)实现,首先我们来看一下 ReentrantLock 的构造方法:
ReentrantLock:
public ReentrantLock(boolean fair) {
// 根据传入的 boolean 变量 fair 来确定使用公平锁或非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock 默认的无参构造方法使用的是非公平锁,我们就先来看一下非公平锁的加锁实现:
ReentrantLock.NonfairSync:
final void lock() {
// CAS 将 AQS 的 state 变量从0改为1,成功即为获得锁
if (compareAndSetState(0, 1))
// 成功则设置独占锁的拥有者线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 如果 CAS 失败,证明锁已经被占用,则调用 AQS 的 acquire 方法来进行锁重入的判断、再次尝试获取锁、入队阻塞等待等操作
acquire(1);
}
AQS的acquire方法我们在 AQS(AbstractQueuedSynchronizer)源码解析(独占锁部分)这篇文章中进行过详细的分析。方法的第一步就是调用由子类实现的 tryAcquire 方法通过操作 state 变量尝试以独占模式获取锁,这里可能会有疑问,上面的 CAS 操作失败了不就说明锁已经被占用了么,不是应该直接入队阻塞等待锁释放时被唤醒么,这里为什么还会再次尝试获取锁呢?因为到这里可能锁已经被释放,也有可能是同一个线程占用的锁,这样我们可以实现锁重入功能,只有这些条件都不满足才会进行入队阻塞等待操作。我们来分析 tryAcquire 方法的实现:
ReentrantLock.NonfairSync:
protected final boolean tryAcquire(int acquires) {
/* 尝试非公平独占模式获取锁 */
return nonfairTryAcquire(acquires);
}
ReentrantLock.Sync:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取 state 变量
int c = getState();
if (c == 0) {
// 如果 state 变量的值为0,说明锁已经被释放,则再次尝试 CAS 操作获取锁
if (compareAndSetState(0, acquires)) {
// 成功则设置独占锁的拥有者线程
setExclusiveOwnerThread(current);
return true;
}
}
// 判断独占锁的拥有者线程是否是当前线程
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
// state 变量是 int 型,这里如果 nextc 小于0说明已经超过 int 的最大值,抛出 Error 已经超过最大锁定计数
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// state 变量,同时也代表锁定计数
setState(nextc);
return true;
}
return false;
}
非公平锁的重入功能也就是在这里实现的了。下面我们来看公平锁的加锁实现:
ReentrantLock.FairSync:
final void lock() {
acquire(1);
}
我们发现与非公平锁的 lock 方法的区别就在于非公平锁首先会尝试 CAS 修改 AQS 的 state 变量来尝试获取锁,失败了才会调用 acquire 方法,而公平锁直接调用 acquire 方法。我们继续分析公平锁对于 tryAcquire 方法的实现:
ReentrantLock.FairSync:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 这里的判断多了一步,判断当前线程是否是下一个能够优先获得锁的线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 锁重入功能的实现与非公平锁的实现相同
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
我们发现,公平锁与非公平锁对 tryAcquire 方法的实现的唯一区别就是公平锁只有在当前线程是下一个能够优先获得锁的线程的情况下才会去尝试获取锁。hasQueuedPredecessors 方法主要确认以下几种情况:
- 等待队列为空
- 当前线程所在的节点是头结点
- 当前线程所在的节点是头结点的后继节点
到这里,我们已经可以分析出公平锁和非公平锁的区别的原理了:
- 公平锁会严格的按照线程入队的顺序来获取锁,我们在上文的分析也看到了,公平锁的 lock 方法并不会直接尝试获取锁,只有在当前线程是下一个能够优先获得锁的线程的情况下才会尝试获取锁(这里我们抛开锁重入的情况),我们在AQS独占锁源码解析的文章中提到,AQS 依赖 FIFO(first-in-first-out 先进先出)等待队列来完成线程的排队工作,当 hasQueuedPredecessors 方法返回 true 时,说明当前线程不是下一个应该获取锁的线程,公平锁就是靠这样的控制来保证“公平”的。
- 相反,非公平锁并不会严格的按照线程入队的顺序来获取锁,每一个新加入的线程都会首先尝试获取锁,只有锁获取失败时才会进行入队阻塞等待,所以它是“非公平”的。
最后我们来看锁释放的流程,公平锁和非公平锁的锁释放流程是一样的:
ReentrantLock:
public void unlock() {
sync.release(1);
}
这里的 release 方法我们在 AQS 独占锁源码解析的文章中同样进行过详细的分析,AQS 的 release 方法首先会尝试调用由子类实现的 tryRelease 方法来尝试设置 state 变量来释放独占锁,锁完全释放后,会对后继节点进行唤醒操作,这个流程我们已经分析过,不再赘述。我们来看 ReentrantLock 的对 tryRelease 方法的实现:
ReentrantLock.Sync:
protected final boolean tryRelease(int releases) {
// 因为有锁重入的存在,所以这里要递减锁计数
int c = getState() - releases;
// 必须由同一个线程进行加锁和释放锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 标识锁是否已经空闲
boolean free = false;
if (c == 0) {
free = true;
// 如果锁计数为0,说明锁已经被完全释放,则将锁的拥有者线程置为 null
setExclusiveOwnerThread(null);
}
// 设置当前 state
setState(c);
// 返回锁的是否空闲状态
return free;
}
这样锁释放的流程就分析完成了。
ReentrantLock 还支持 Condition,ReentrantLock 的 Condition 完全基于 AQS 的 ConditionObject 实现,我们已经分析过 ConditionObject 源码,不明白的同学可以前往进行查阅学习。到这里,ReentrantLock 的源码分析就完成了。