一、总览
二、源码分析
2.1 人口
public Condition newCondition() {return sync.newCondition();}
final ConditionObject newCondition() {return new ConditionObject();}
public class ConditionObject implements Condition, java.io.Serializable {private static final long serialVersionUID = 1173984872572414699L;// 指向条件队列的第一个node节点private transient Node firstWaiter;// 指向条件队列的最后一个node节点private transient Node lastWaiter;/*** Creates a new {@code ConditionObject} instance.*/public ConditionObject() { }}
await方法:
public final void await() throws InterruptedException {// 判断当前线程是否是中断if (Thread.interrupted())throw new InterruptedException();// 将调用await方法的线程包装成node并加入条件队列中,并返回当前nodeNode node = addConditionWaiter();// 完全释放当前线程对应的锁,// 为什么要释放锁那?加着锁挂起后,谁还能救你那int savedState = fullyRelease(node);// 0 在condition队列挂起期间未接收中断信号// -1 在condition队列期间接收到中断信号// 1 在condition队列挂起期间未接收到中断信号,但是在迁移到“阻塞队列”过程中,接收过中断信号int interruptMode = 0;// isOnSyncQueue返回true表示当前线程对应的node已经迁移到“阻塞队列”// false 说明当前node 仍然还在条件队列中,需要继续parkwhile (!isOnSyncQueue(node)) {// 挂起当前node对应的线程,接下去去看看signal过程LockSupport.park(this);// 什么时候会被唤醒?都有哪几种情况// 1、常规路径:外部线程获取到锁之后,调用signal()方法 转移条件队列的头节点到阻塞队列,当// 这个节点获取到锁后,会唤醒// 2.转移至阻塞至阻塞队列后,发现阻塞队列中的前驱节点状态是 取消状态,此时会唤醒当前节点// 3.当前节点挂起期间时,被外部线程使用中断唤醒// checkInterruptWhileWaiting: 就算在condition队列挂起期间,线程发生中断了,// 对应的node也会被迁移到“阻塞队列”if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}// acquiredQueued:竞争队列的逻辑if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;// 考虑下 node.nextWaiter != null 条件什么时候成立那// 其实是node在条件队列内时,如果被外部线程中断唤醒时,会加入到阻塞队列,// 但是并未设置nextWaiter != nullif (node.nextWaiter != null) // clean up if cancelled// 清理条件队列取消状态的节点unlinkCancelledWaiters();// 条件成立:说明挂起期间 发生过中断(1.条件队列内的挂起 2.条件队列之外的挂起)if (interruptMode != 0)// 条件队列内发生中断,此外await方法抛出中断异常// 条件队列外发生的中断,交给你的业务处理,如果你不处理,那什么事也不会发生reportInterruptAfterWait(interruptMode);}
- 将当前线程包装成ConditionNode加入到条件队列中
- 释放锁资源
- while循环,如果当前线程对应的node已经迁移到“阻塞队列”,那就park挂起它,然后开始走acquireQueued逻辑,开始竞争锁 (这里线程什么时候会到“阻塞队列”中那)
private int checkInterruptWhileWaiting(Node node) {// thread.interrupted 返回当前线程中断标记位,并且重置当前标记为falsereturn Thread.interrupted() ?// transferAfterCancel 这个方法只有在线程是被中断唤醒时才会调用(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :0;}final boolean transferAfterCancelledWait(Node node) {// 条件成立:说明当前node一定是在条件队列内,因为signal迁移节点// 到阻塞队列时,会将节点的状态修改为0if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {// 中断唤醒的node也会被加入到阻塞队列中enq(node);// true:表示是在条件队列内被中断的return true;}// 执行到这里有几种情况?// 1、当前node已经被外部线程调用 signal方法将其迁移到阻塞队列内// 2、当前node正在被外部线程调用 signal方法将其迁移到阻塞队列中while (!isOnSyncQueue(node))Thread.yield();return false;}
signal方法
public final void signal() {// 判断调用signal方法的线程是否是独占锁持有线程,如果不是,直接抛出异常if (!isHeldExclusively())throw new IllegalMonitorStateException();Node first = firstWaiter;if (first != null)doSignal(first);}
private void doSignal(Node first) {do {// firstWaiter = first.nextWaiter 因为当前first马上要出条件队列// 如果当前节点的下一个节点是NULL,说明条件队列只有当前一个节点了。。// 当前出队后,整个队列就空了// 所以需要更新lastWaiter = nullif ( (firstWaiter = first.nextWaiter) == null)lastWaiter = null;// 当前first节点 出条件队列,断开和下一个节点的关系first.nextWaiter = null;// transferForSignal(first)// true:当前first节点迁移到阻塞队列成功 false迁移失败} while (!transferForSignal(first) &&(first = firstWaiter) != null);}
final boolean transferForSignal(Node node) {// cas 修改当前节点的状态,修改为0,因为当前节点马上要迁移到 阻塞队列中// 成功:当前节点在条件队列中状态正常// 失败: 1、取消状态(线程await时,未持有锁,最终线程对应的node会设置为取消状态)// 2、node对应的线程挂起期间,被其他线程使用中断信号 唤醒过(就会主动进入到阻塞队列中),这时候也会修改状态为0if (!node.compareAndSetWaitStatus(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).*/Node p = enq(node);int ws = p.waitStatus;// 条件一:ws >0 成立:说明前驱节点状态在阻塞队列中是取消状态,唤醒当前节点// 条件二: 前置条件(ws <= 0) // compareAndSetWaitStatus(p,ws,Node.SIGNAL)表示设置前驱节点状态 SIGANAL状态成功// compareAndSetWaitStatus(p,ws,NOde.SIGNAL)返回false ==> 什么时候返回false// 当前驱node对应的线程是lockInterrupt 入队的node时,是会响应中断的,外部线程给前驱线程中断// 信号之后,前驱node状态修改为取消状态,并且执行出队逻辑// 前驱节点状态只要不是0 或者-1,那么就唤醒当前线程if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))LockSupport.unpark(node.thread);return true;}