Condition源码解读(二)
本章我们继续将Condition的最后一个方法signal方法,如果前面没有看过的可以点击LockSupport与Condition解析来看看Condition解读的前半部分。
signal方法:
public final void signal() {if (!AbstractQueuedLongSynchronizer.this.isHeldExclusively()) {throw new IllegalMonitorStateException();} else {Node var1 = this.firstWaiter;if (var1 != null) {this.doSignal(var1);}}}
signal方法的主要作用就是将线程从Condition队列中唤醒,前面已经讲述过在Condtion的子类ConditionObject内部通过链表来维护整个Condtion队列,并且含有两个属性firstWaiter和lastWaiter分别表示队列头和队列尾部,分析方法首先进行检查当前线程是否持有独占锁。目的是保证持有锁的线程才能调用signal方法来唤醒线程,通过判断之后开始从Condition队列中取出队首线程,随后开始调用doSignal方法来唤醒线程
private void doSignal(Node var1) {do {if ((this.firstWaiter = var1.nextWaiter) == null) {this.lastWaiter = null;}var1.nextWaiter = null;} while(!AbstractQueuedLongSynchronizer.this.transferForSignal(var1) && (var1 = this.firstWaiter) != null);}
在dosignal中首先将后面的node设置为链表头部,如果后续没有node则将尾链表置为null。
同时调用transferForSignal(first)
尝试将节点转移到同步队列,如果转移失败(返回false)且队列还有节点(firstWaiter != null),继续处理下一个节点。
下面我们来看看transferForSignal
方法是如何进行转移的。
final boolean transferForSignal(Node var1) {if (!compareAndSetWaitStatus(var1, -2, 0)) {return false;} else {Node var2 = this.enq(var1);int var3 = var2.waitStatus;if (var3 > 0 || !compareAndSetWaitStatus(var2, var3, -1)) {LockSupport.unpark(var1.thread);}return true;}}
首先进行状态位的CAS设置,如果无法设置表明状态已经改变了直接返回false,表示无法入队。
之后进行入队enq操作(内部是一个循环不断的CAS操作保证能入队)入队完毕之后则查看当前节点状态如果还是阻塞状态则直接调用unpark来唤醒当前线程。
1. 先判断当前线程是否持有当前锁没有则抛出异常
2. 取出Condition队列中的一个首节点尝试入队和唤醒操作
3.失败则再次循环从队伍中取出节点
4.在尝试入队的方法总首先会判断状态值是否符合不符合则直接返回false,符合则会通过CAS循环入队操作,最后判断状态是否为阻塞,为阻塞则直接调用unpark方法进行唤醒操作。
至此Condtion的两个方法已经介绍完毕。
总结:
await方法:
signal方法:
设计精髓
-
双队列分离:条件队列(等待条件)与同步队列(竞争锁)分离
-
状态驱动:
waitStatus
精确控制节点生命周期 -
无锁算法:CAS 操作保证线程安全
-
协作式唤醒:前驱节点负责唤醒后继
-
资源继承:await() 返回时自动恢复原始锁状态
典型应用场景
-
生产者-消费者:不同条件控制队列空/满
-
线程池任务调度:工作线程等待任务到达
-
资源池管理:连接可用性通知
-
屏障实现:所有线程到达后同时释放
-
状态机转换:特定状态变更触发操作