3.1.3 isOnSyncQueue
isOnSyncQueue方法是检查此节点是否在同步队列中,具体源码如下:
先看第一个if语句,如果状态是CONDITION或者prev参数是null,说明此节点是在条件队列中,返回为false。再来看第二个if,我们知道,prev和next都是同步队列中的节点连接是用的prev和next,所以如果两个属性不为null,说明此节点是在同步队列中,所以node.next不为null则需要返回true。如果两个if都不成立,说明这个节点状态是0且prev不为null,即属于我们中CAS进入同步队列的情况,则我们会通过findNodeFromTail方法来确认是不是这种情况
3.1.3.1 findNodeFromTail
如果此时tail就是node的话,说明node在同步队列中,如果不是就像前遍历。我们再回到await方法:
如果不在同步队列中,则此线程就被park方法阻塞了,只有当线程被唤醒才会在这里开始继续执行下面代码。
3.2 wait—唤醒后
我们再来看看await唤醒后的情形:
我们需要注意的是,线程在这里被唤醒有两种情况:
- 其他线程调用了doSignal或doSignalAll,
- 线程被中断。
我们需要确定我们被唤醒的情况是哪种,这里是通过checkInterruptWhileWaiting方法来判断。但在讲这个方法前,我们需先了解这个interruptMode有几种状态:
除了上面两种,还有一种初始态0,它代表线程没有被中断过,不做任何处理。
3.2.1 checkInterruptWhileWaiting
我们看下代码,首先我们会检查中断标志位,如果interrupted方法返回false,说明没发生中断,方法最终返回0;如果返回了true,则说明中断了,则我们需要通过transferAfterCancelledWait方法进一步检查其他线程是否执行了唤醒操作。
3.2.1.1 transferAfterCancelledWait
我们先看第一个if条件,如果条件中的CAS操作成功,说明此时的节点肯定是在条件队列中,则我们调动 enq 方法将此节点放入到同步队列中,然后返回true。但是这里需要特别注意,这个节点的nextWaiter还没置为null;如果CAS失败,说明这个节点可能已经在同步队列中或者在入队的过程中,所以我们通过while循环等待此节点入队后返回false。
我们再回到调用transferAfterCncelled 的 checkInterruptWhileWaiting方法中,根据transferAfterCancelledWait方法返回值我们最终会返回REINTERRUPT或THROW_IE。
然后我们返回到调用checkInterruptWhileWaiting方法的await方法中。
我们可以看到,如果返回值不为0,则直接break跳出循环,如果为0,则再次回到while条件检查是否在同步队列中。最后我们看最后剩下的三个if语句:
- 通过acquireQueued方法来获取锁,这个方法在第二篇中详细讲过,acquireQueued返回true(即获取锁的的过程中被中断了),我们再将interruptMode为0置为REINTERRUPT。
- 如果node的nextWaiter不是null。我们会通过unlinkCancelledWaiters方法将条件队列中所有不为CONDITION的节点移除。
- 最后一个if,线程拿到锁了,且节点没在同步队列和条件队列中,await方法其实算完成了,我们这时候只需要对中断进行善后处理。如果interruptMode不为0,说明线程是被中断过的,需要通过reportInterruptAfterWait对中断进行处理。
3.2.1.2 reportInterruptAfterWait
如果是THROW_IE,就是抛异常,如果是REINTERRUPT,就再自我中断一次。
四 总结
好了,AQS如何解决线程同步与通信问题,指北君就分析完了,这里我再总结一下:
AQS通过ConditionObject类来实现条件变量,并通过其唤醒方法、阻塞方法来进行线程的通信。当线程获取锁之后,可以通过signal、signalAll等唤醒方法将条件队列中被阻塞的线程节点转移到同步队列中,然后唤醒去竞争锁;也可以通过wait方法将自己包装成节点并放入条件队列中,然后等待被其他线程唤醒或中断。
到这里,AQS系列就写完了,希望大家能好好体会AQS设计的精妙~