前言
前面已经分析过Condition:
常见并发工具的使用和原理解析——Condition(重点在第五节)
有兴趣可以看看,本篇文章我们着重讲源码。
- await:把当前线程阻塞挂起
- signal:唤醒阻塞的线程
一、await()
调用 Condition 的 await()方法(或者以 await 开头的方法),会使当前线程进入阻塞队列并释放锁,同时状态变成等待状态。当从await()方法返回时,当前线程一定获取了Condition相关联的锁。
public final void await() throws InterruptedException {
//表示 await 允许被中断
if (Thread.interrupted())
throw new InterruptedException();
//创建一个节点,节点状态为condition,采用的数据结构是链表——单向链表
Node node = addConditionWaiter();
//释放锁(包括重入的),得到锁的状态,并唤醒 AQS 队列中的一个线程
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1.1 addConditionWaiter()
- 查找等待队列最后一个节点lastWaiter,如果它不为空且状态不为等待状态,把这个节点从等待队列中移除。
- 将当前线程构建成一个Node,waitStatus为等待状态。
- 设置此节点为当前lastWaiter节点的下一个节点,再设置当前节点为lastWaiter。如果
1.2 fullyRelease(Node node)
- release方法是释放锁的方法(下面会继续分析),如果释放锁成功,返回重入锁的次数
- 如果释放锁失败,则将当前节点的waitStatus设置成取消(CANCELLED)状态。
疑问:
waitStatus=CANCELLED的节点,后续会清除出队列,那么该线程被清除了后,最终会怎么样呢?就此消失了吗?
1.2.1 release(int arg)
- 释放锁
- 唤醒下一个同步队列的线程。head是同步队列的头节点,unparkSuccessor(h)是唤醒头节点
贴一下unpartSuccessor(h)代码,它的作用是唤醒下一个线程:
- 替换当前节点的waitStatus为0
- 获取当前节点的下一节点并设置为s,如果s为空或者s的waitStatus为取消,从后往前遍历同步队列,从而达到设置“最靠近头节点且waitStatus<0”的Node为s
- 唤醒s线程
waitStatus>0,只有一种情况,就是为取消状态,取消(CANCELLED)状态为1。
疑问:waitStatus=0有这个状态吗?在代码枚举中没有看到。
二、singal()
public final void signal() {
//先判断当前线程是否获得了锁,这个判断比较简单,直接用获得锁的线程和当前线程相比即可
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 拿到 Condition队列上第一个节点
Node first = firstWaiter;
if (first != null)
//主要逻辑
doSignal(first);
}
2.1 Condition.doSignal
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
2.2 AQS.transferForSignal
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//更新节点的状态为 0,如果更新失败,只有一种可能就是节点被 CANCELLED 了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
//调用 enq,把当前节点添加到AQS 队列。并且返回返回按当前节点的上一个节点,也就是原tail 节点
Node p = enq(node);
int ws = p.waitStatus;
// 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next节点需要停止阻塞),
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 唤醒节点上的线程.
LockSupport.unpark(node.thread);
//如果 node 的 prev 节点已经是signal 状态,那么被阻塞的 ThreadA 的唤醒工作由 AQS 队列来完成
return true;
}
不理解第3点:为什么原尾节点为取消状态或更新状态为SIGNAL失败,要唤醒当前节点的线程,为的是什么?
三、被阻塞的线程唤醒后的逻辑
前面在分析 await 方法时,线程会被阻塞。而通过 signal被唤醒之后又继续回到上次执行的逻辑中标注为红色部分的代码。
四、Condition 总结
4.1 await 和 signal 的总结
我把前面的整个分解的图再通过一张整体的结构图来表述, 线程 awaitThread 先通过 lock.lock()方法获取锁成功后调用了 condition.await 方法进入等待队列,而另一个线程 signalThread 通过 lock.lock()方法获取锁成功后调用了 condition.signal 或者 signalAll 方法,使得线程awaitThread 能够有机会移入到同步队列中,当其他线程释放 lock 后使得线程 awaitThread 能够有机会获取lock,从而使得线程 awaitThread 能够从 await 方法中退出执行后续操作。如果 awaitThread 获取 lock 失败会直接进入到同步队列。
- 阻塞: await()方法中,在线程释放锁资源之后,如果节点不在 AQS 等待队列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁
- 释放: signal()后,节点会从 condition 队列移动到 AQS等待队列,则进入正常锁的获取流程
了解完 Lock 以及 Condition 之后,意味着我们对于 J.U.C里面的锁机制以及线程通信机制有了一个全面和深入的了解, 后面我们来看看其他比较常用的一些工具。