Java多线程工具之ReentrantLock分析
ReentrantLock 可重入锁,应该是除了 synchronized 关键字外用的最多的线程同步手段了,虽然JVM维护者疯狂优化 synchronized 使其已经拥有了很好的性能。但 ReentrantLock 仍有其存在价值,例如可以感知线程中断,公平锁模式,可以指定超时时间的抢锁等更细粒度的控制都是目前的 synchronized 做不到的。
案例
用一个最简单的案例引出我们的主角
public class ReentrantLockDemo {
// 默认是非公平锁和 synchronized 一样
private static ReentrantLock reentrantLock = new ReentrantLock();
public void printThreadInfo(int num) {
reentrantLock.lock();
try {
System.out.println(num + " : " + Thread.currentThread().getName());
System.out.println(num + " : " + Thread.currentThread().toString());
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
IntStream.rangeClosed(0, 3)
.forEach(num -> executorService
.execute(() -> new ReentrantLockDemo().printThreadInfo(num))
);
}
/**
* 输出:
* 0 : pool-1-thread-1
* 0 : Thread[pool-1-thread-1,5,main]
* 3 : pool-1-thread-4
* 3 : Thread[pool-1-thread-4,5,main]
* 1 : pool-1-thread-2
* 1 : Thread[pool-1-thread-2,5,main]
* 2 : pool-1-thread-3
* 2 : Thread[pool-1-thread-3,5,main]
*/可以看到使用起来也很简单,而且达到了同步的效果。废话不多说一起来瞅一瞅 lock() 和 unlock() 两个同步方法是怎么实现的。
源码分析
公平锁与非公平锁
公平锁顾名思义。就是每个线程排队抢占锁资源。而非公平锁线程什么时候能执行更多的看缘分,例如一个线程需要执行临界区代码,不管之前有多少线程在等,直接去抢锁,说白了就是插队。对于 ReentrantLock 的实现,从构造器看出,当我们传入 true 代表选择了公平锁模式
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}为什么先看公平锁实现,而不是默认的非公平锁,因为 synchronized 就是非公平锁,1.7开始 synchronized 的实现改变了,并且基本借鉴了 ReentrantLock 的实现,加入了自旋,偏向锁减少系统调用,所以如果需要非公平锁且不需要特别精细的控制,完全没有必要因为性能选择 ReentrantLock 了。
AQS 结构
从案例中的 lock 方法进入
- ReentrantLock.FairSync#lock
final void lock() {
// 要一把锁,向谁要锁?
acquire(1);
}在继续深入之前让我们先熟悉一下 AbstractQueuedSynchronizer(AKA :AQS) 的结构
- 首先继承了 AbstractOwnableSynchronizer ,主要属性:
// 保存当前持有锁的线程
private transient Thread exclusiveOwnerThread;- AQS 自身主要属性:
// 阻塞队列的头
private transient volatile Node head;
// 阻塞队列的尾
private transient volatile Node tail;
// 同步器的状态
private volatile int state;从 head 和 tail 可以猜想到,AQS 应该是用一个链表作为等待队列,给等待的线程排队, status 字段默认是0,一旦锁被某个线程占有就 +1,那为啥要用int呢? 如果当前持有锁的这个线程(exclusiveOwnerThread)还要再来把锁,那状态还可以继续 +1,也就实现了可重入。
- 上面的 Node 节点长啥样呢,不要被注释中 CLH 锁高大上的名称吓到,其实就是双向链表,主要属性:
// 标识次节点是共享模式
static final Node SHARED = new Node();
// 标识次节点是独占模式
static final Node EXCLUSIVE = null;
// 节点里装着排队的线程
volatile Thread thread;
// 节点里装的线程放弃了,不抢锁了,可能超时了,可能中断了
static final int CANCELLED = 1;
// 下一个节点里的线程等待被通知出队
static final int SIGNAL = -1;
// 节点里装的线程在等待执行条件,结合 Condition 使用
static final int CONDITION = -2;
// 节点状态需要被传播到下一个节点,主要用在共享模式
static final int PROPAGATE = -3;
// 标识节点的等待状态,初始0,取值是上面的 -3 ~ 1
volatile int waitStatus;
// 前一个节点
volatile Node prev;
// 后一个节点
volatile Node next;
// 指向下一个等待条件 Condition
Node nextWaiter;去掉一些普通情况不会涉及的属性,如果有四个线程竞争,结构如下图所示:
可以看到就是一个标准的头节点为空的双链表,为什么头节点是空?
公平锁加锁
- AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
// 如果尝试拿锁没成功,那就进等待队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 检测到线程被中断了,因为重置了中断信号但没做处理,再设置下中断位,让用户去处理,中断标准操作
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}- ReentrantLock.FairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 取AQS的 state 值
int c = getState();
// 当前没有线程持有锁
if (c == 0) {
// 如果没有其他线程在排队(公平)
if (!hasQueuedPredecessors() &&
// 这里可能存在竞争 CAS 试着去抢一次锁
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;
}- AbstractQueuedSynchronizer#hasQueuedPredecessors
// 返回 false 代表不需要排队,true 代表要排队
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// h == t 头等于尾只可能是刚初始的状态或者已经没有节点等待了
// h.next == null ? 下面介绍进队的过程中,如果其他线程与此同时 tryAcquire 成功了,会把之前的head.next置为空,说明被捷足先登了,差一点可惜
// 如果到最后一个判断了,也就是队列中至少有一个等待节点,直接看第一个等待节点是不是自己,如果不是自己就乖乖排队去
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}- AbstractQueuedSynchronizer#addWaiter
tryAcquire如果没有拿到锁,就需要进等待队列了,变成一个 Node 实例
// 这里 mode 为独占模式
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 如果尾节点不为空,说明等待队列已经初始化过
if (pred != null) {
node.prev = pred;
// 尝试把自己放到队尾
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 进来这里说明,等待队列没有被初始化过,或者尝试失败了
enq(node);
return node;
}- AbstractQueuedSynchronizer#enq
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 如果尾节点是空,说明队列没有初始化
if (t == null) {
// 初始化一个空节点(延迟加载),head ,tail都指向它
if (compareAndSetHead(new Node()))
tail = head;
}
// 一直尝试把自己塞到队尾(自旋)
else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}- AbstractQueuedSynchronizer#acquireQueued
addWaiter方法已经把等待线程包装成节点放到等待队列了,acquireQueued自旋抢锁,醒了就抢,为啥要返回中断标识呢?主要是为了给一些需要处理中断的方式复用,例如 ReentrantLock#lockInterruptibly,以及带超时的锁,以及Condition
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 这边逻辑开始绕起来了
for (;;) {
// 拿前一个节点
final Node p = node.predecessor();
// 前一个节点是head,说明自己排在第一个
if (p == head &&
// 在让出cpu前再试一次,此时可能锁持有者已经让位了
tryAcquire(arg)) {
// 抢到锁了
setHead(node);
// 把之前没用的头节点释放
p.next = null; // help GC
failed = false;
return interrupted;
}
// 两次尝试都失败了,只能准备被挂起,让出cpu了(调了内核,重量级)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 普通的锁不处理中断异常,不会进这个方法
if (failed)
cancelAcquire(node);
}
}
private void setHead(Node node) {
// 把头节点设为自己
head = node;
// 因为已经抢到锁了,不需要记录这个线程在等待了,保持了头节点中线程永远为 null
node.thread = null;
node.prev = null;
}- AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 已经告诉前一个节点自己需要被通知了
return true;
if (ws > 0) {
// 只有 CANCELLED 这个状态大于0,如果前面的节点不排队了,就一直找到一个没 CANCELLED 的
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
}
// 进到这里,只剩下PROPAGATE(共享锁时候才有) CONDITION(本文不涉及) 和 未赋值状态也就是0,
else {
// 这里把 默认状态0 置为 -1,也就代表着后面有线程在等着被唤醒了
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 返回false,就暂时不会让线程挂起,继续自旋,直到返回true
return false;
}- AbstractQueuedSynchronizer#parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
// 挂起,标准用法this充当blocker
LockSupport.park(this);
// 一旦恢复,返回线程在挂起阶段是否被中断,此方法会重置中断位
return Thread.interrupted();
}到这里加锁流程就介绍差不多了,用一个最简单流程的图来总结一下:
公平锁解锁
- AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {
Node h = head;
// 如果等待队列已经被初始化过,并且后面有节点等待操作
if (h != null && h.waitStatus != 0)
// 恢复挂起的线程
unparkSuccessor(h);
return true;
}
return false;
}- ReentrantLock.FairSync#tryRelease
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;
// 锁现在没有持有者了
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}- ReentrantLock.FairSync#unparkSuccessor
// node 是头节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// 如果状态不是 CANCELED,就把状态置为初始状态
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// s == null 这个条件成立主要是在共享模式下自旋释放。
if (s == null || s.waitStatus > 0) {
// 把 CANCELED 状态的节点置为空
s = null;
// 因为 head 这条路已经断了,从尾巴开始找到第一个排队的节点,然后把队列接上
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 把第一个排队的节点中的线程唤醒,
LockSupport.unpark(s.thread);
}线程从加锁代码里介绍的 AbstractQueuedSynchronizer#parkAndCheckInterrupt 方法中醒来,继续自旋拿锁。如果此时后面还有人排队就一定能拿到锁了。如图所示:
非公平锁加锁
- ReentrantLock.NonfairSync#lock
final void lock() {
// 不管三七二十一,直接抢锁,如果运气好,锁正好被释放了,就不排队了
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 和上面介绍的公平锁一样,只是 tryAcquire 实现不一样
acquire(1);
}- ReentrantLock.Sync#nonfairTryAcquire
上面公平锁我们已经知道,线程真正挂起前会尝试两次,由于不考虑别人有没有入队,实现非常简单
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果没有线程持有锁,直接抢锁
if (c == 0) {
if (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;
}非公平锁解锁
因为都是独占锁模式,解锁和公平锁逻辑一样。
感知中断锁
如果我们希望检测到中断后能立刻抛出异常就用 lockInterruptibly 方法去加锁,还是建议用 lock 方法,自定义中断处理,更灵活一点。
- ReentrantLock#lockInterruptibly
我们只需要把 ReentrantLock#lock 改成 ReentrantLock#lockInterruptibly 方法就可以获得内部检测中断的锁了
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}- AbstractQueuedSynchronizer#acquireInterruptibly
主要流程和前文介绍的类似
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 一上来就检查下中断,中断直接异常,就没必要抢锁排队了
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}- AbstractQueuedSynchronizer#doAcquireInterruptibly
和正常加锁唯一区别就是这个方法,但是定睛一看是不是似曾相识?最大区别就是把中断标识给去掉了,检测到中断直接抛异常
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
// 大神也偷懒了,因为这个方法,只有独占锁且检查中断这一个应用场景,把节点入队的步骤也揉了进来
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 当线程拿到锁苏醒过来,发现自己挂起过程被中断了,直接抛出异常
throw new InterruptedException();
}
} finally {
// 只要发生了中断异常,就会进取消加锁方法
if (failed)
cancelAcquire(node);
}
}- AbstractQueuedSynchronizer#cancelAcquire
此方法很有东西,只保证该节点失效,然后延迟移出等待队列
private void cancelAcquire(Node node) {
if (node == null)
return;
// 把节点里登记等待的线程去掉,完成这一步此节点已经没有作用了
node.thread = null;
// 下面的三步其实可以放到一个CAS中,直接设置 CANCELLED 状态 ,拿前一个节点,predNext 也必然是自己,但是吞吐量就下来了
// 这里大神,没有这样做也是出于了性能考虑,因为我们已经把等待线程设置成 null 了,所以此节点已经没有任何意义,没有必要去保证节点第一时间被释放,只要设置好 CANCELLED 状态
// 就算后面 CAS 调整等待队列失败了,下次取消操作也会帮着回收。相应地代码复杂度提高了。
/* ----------------------------------------- */
// 找到自己前面第一个没取消的节点,
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 主要是为了下面把链表接上
Node predNext = pred.next;
// 这里逻辑上把当前节点的状态设置成取消,便于检测释放
node.waitStatus = Node.CANCELLED;
/* ----------------------------------------- */
// 如果当前节点是尾节点,就把前一个没取消的节点设成新尾巴
if (node == tail && compareAndSetTail(node, pred)) {
// 把新尾巴的 next 设置成空
compareAndSetNext(pred, predNext, null);
} else {
// 进到这里说明当前节点肯定不是尾节点了
int ws;
// 条件1: 如果前一个非取消节点不是头,也就是还需要排队
// 条件2: 如果前一个节点为 SIGNAL,也就是说后面肯定还有线程等待被唤醒
// 条件3: 如果前一个节点也取消了,说明前一个节点也取消了,还没来得及设置状态
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
// 当前节点后一个没取消的话,就接到前一个正常的节点后面
compareAndSetNext(pred, predNext, next);
} else {
// 前一篇文章解锁部分讲过,会把下一个节点中的线程恢复,然后把后继节点接上
unparkSuccessor(node);
}
// 有点花里胡哨,直接 = null不行么,
node.next = node; // help GC
}
}来张图说明下,假如我们目前等待队列里有7个线程:
等待条件锁
上篇文章看源码过程中,AQS中有个 CONDITION 状态没有研究
static final int CONDITION = -2;ReentrantLock 中的 newCondition 等 Condition 相关方法正是基于 AQS 中的实现的,让我们先大致了解一波作用和用法
Condition简介
Condition 类似于 Object 中的 wait 和 notify ,主要用于线程间通信,最大的优势是 Object 的 wait 是把线程放到当前对象的等待池中,也就是说一个对象只能有一个等待条件,而 Condition 可以支持多个等待条件,举个例子,商品要等至少三个人预定了才开始发售,第一个预定的减500,第二三两个减100。正式发售之后恢复原价。
public class ReentrantLockConditionDemo {
private final ReentrantLock reentrantLock = new ReentrantLock();
private final Condition wait1 = reentrantLock.newCondition();
private final Condition wait2 = reentrantLock.newCondition();
private int wait1Count = 0;
private int wait2Count = 0;
public void buy() {
int price = 999;
reentrantLock.lock();
try {
while (wait1Count++ < 1) {
System.out.println(Thread.currentThread().getName() + "减500");
wait1.await();
price -= 500;
}
wait1.signal();
while (wait2Count++ < 2) {
System.out.println(Thread.currentThread().getName() + "减100");
wait2.await();
price -= 100;
}
wait2.signal();
System.out.println(Thread.currentThread().getName() + "到手价" + price);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
ReentrantLockConditionDemo reentrantLockConditionDemo = new ReentrantLockConditionDemo();
IntStream.rangeClosed(0, 4)
.forEach(num -> executorService
.execute(reentrantLockConditionDemo::buy)
);
}
/**
* 输出:
*
* pool-1-thread-1减500
* pool-1-thread-2减100
* pool-1-thread-3减100
* pool-1-thread-4到手价999
* pool-1-thread-5到手价999
* pool-1-thread-1到手价499
* pool-1-thread-2到手价899
* pool-1-thread-3到手价899
*/
}- ReentrantLock#newCondition
先来看条件的创建,需要基于锁对象使用 newCondition 去创建
public Condition newCondition() {
return sync.newCondition();
}
final ConditionObject newCondition() {
// ConditionObject 是 AQS 中对 Condition 的实现
return new ConditionObject();
}ConditionObject结构
上一篇文章中介绍了 Node 结构,这里条件也使用了这个节点定义了一个单链表,统称为条件队列,上一篇介绍统称同步队列。条件队列结构相当简单就不单独画图了。
// 条件队列头
private transient Node firstWaiter;
// 条件队列尾
private transient Node lastWaiter;
// 因为默认感知中断,需要考虑如何处理
// 退出条件队列时重新设置中断位
private static final int REINTERRUPT = 1;
// 退出条件队列时直接抛异常
private static final int THROW_IE = -1;条件队列入队
- AbstractQueuedSynchronizer.ConditionObject#await
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 到条件队列中排队,下文详解
Node node = addConditionWaiter();
// 此方法比较简单,就是调用前一篇讲过的 release 方法释放锁(调用 await 时必定是锁的持有者)
// savedState 是进入条件队列前,持有锁的数量
// 失败会直接抛出异常,并且最终把节点状态设置为 CANCELLED
int savedState = fullyRelease(node);
int interruptMode = 0;
// 判断在不在同步队列(当调用signal之后会从条件队列移到同步队列),此判断很简单:节点状态是 CONDITION 肯定 false,否则就到同步队列中去找
while (!isOnSyncQueue(node)) {
// 挂起
LockSupport.park(this);
// 检查是不是因为中断被唤醒的,下文详解
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 上一篇介绍过acquireQueued自旋抢锁,如果抢到锁了,并且中断模式不是 -1(默认0),就记录中断模式为1,表示需要重新设置中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清除条件队列中取消的节点
if (node.nextWaiter != null)
// 下文详解,在addConditionWaiter方法中也有用到
unlinkCancelledWaiters();
// 处理中断
if (interruptMode != 0)
// 1:再次中断 -1:抛出异常
reportInterruptAfterWait(interruptMode);
}- AbstractQueuedSynchronizer.ConditionObject#addConditionWaiter
加入条件队列
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果条件队列最后一个节点取消了,就清理
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 新建一个 waitStatus = -2 的节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 下面是简单的单链表操作,之前同步队列入队用的 CAS 操作,因为会有很多线程去抢锁,而线程进入条件队列一定是拿到锁了,不满足条件了,所以不存在并发问题
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}- AbstractQueuedSynchronizer.ConditionObject#unlinkCancelledWaiters
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
// 辅助变量,用于接尾巴,trail始终等于循环中当前节点t的上一个不是取消状态的节点
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
// 判断当前节点有没有取消
if (t.waitStatus != Node.CONDITION) {
// 断当前节点链
t.nextWaiter = null;
// trail == null 说明目前条件队列里面全取消了
if (trail == null)
// 头节点指向第一个没取消的节点
firstWaiter = next;
else
// trail 是 t 的前一个节点,也就是踢出了 t
trail.nextWaiter = next;
// 如果最后一个节点取消了,那需要改一下尾指针
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}- AbstractQueuedSynchronizer.ConditionObject#checkInterruptWhileWaiting
上文 await 方法中,线程一旦唤醒会先检查中断
private int checkInterruptWhileWaiting(Node node) {
// 没中断,返回0,中断了需要放回同步队列
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}- AbstractQueuedSynchronizer#transferAfterCancelledWait
// 如果
final boolean transferAfterCancelledWait(Node node) {
// 把因为中断醒来的节点,设置状态为全新的节点,从条件队列放入同步队列
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
// 上面改状态为什么要 CAS ? 如果中断唤醒的同时被 signal 唤醒了,在 signal 入队成功之前让出cpu,但是不释放锁
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}条件队列出队
单个唤醒和唤醒所以掉的方法类似,看一个单个唤醒流程就可
- AbstractQueuedSynchronizer.ConditionObject#signal
public final void signal() {
// 如果持有锁的线程不是当前线程就抛异常,也就是只有获得锁的线程可以执行唤醒操作
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
// 通知条件队列中的第一个节点,也就是等的最久的节点
if (first != null)
doSignal(first);
}- AbstractQueuedSynchronizer.ConditionObject#doSignal
private void doSignal(Node first) {
do {
// 把 first 断链
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// 如果转移到同步队列失败了,并且还有条件队列不为空就唤醒下一个
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}- AbstractQueuedSynchronizer#transferForSignal
final boolean transferForSignal(Node node) {
// 如果节点取消了,转移失败
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 这里的 p 是 node 在同步队列里的前驱节点
Node p = enq(node);
int ws = p.waitStatus;
// 看过上一篇文章应该有映像,只要是进同步队列,都需要把前一个节点状态设为 -1
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 如果取消了,或者状态设置失败,唤醒后继续挂起
LockSupport.unpark(node.thread);
return true;
}最后按照惯例结合上面的案例,画张图总结下:
总结
至此,总算看完了 ReentrantLock 常规的加锁解锁源码,好好体会下 AQS 的结构,Doug Lea 大神牛B。




