其实signal()或者signalAll()会对取消的节点或者短暂中间状态的节点进行解除阻塞,但是正常情况下,它们的操作结果是把阻塞等待时间最长的一个或者所有节点重新加入到AQS的同步等待队列中。例如,上面的例子调用signal()方法后如下:
这样子,相当于线程thread-1重新加入到AQS同步等待队列中(从条件等待队列中移动到同步等待队列中),并且开始竞争头节点,一旦竞争成功,就能够解除阻塞。这个时候从逻辑上看,signal()方法最终解除了对线程thread-1的阻塞。await()的其他变体方法的原理是类似的,这里因为篇幅原因不再展开。这里小结一下Condition的显著特点:
1、同时依赖两个同步等待队列,一个是AQS提供,另一个是ConditionObject提供的。
2、await()方法会释放AQS同步等待队列中的阻塞节点,这些节点会加入到条件等待队列中进行阻塞。
3、signal()或者signalAll()会把条件等待队列中的节点重新加入AQS同步等待队列中,并不解除正常节点的阻塞状态。
4、接第3步,这些进入到AQS同步等待队列的节点会重新竞争成为头节点,接下来的步骤其实也就是前面分析过的独占模式下的AQS的运作原理。
取消获取资源(cancelAcquire)新节点加入等待队列失败导致任何类型的异常或者带超时版本的API调用的时候剩余超时时间小于等于零的时候,就会调用cancelAcquire()方法,用于取消该节点对应节点获取资源的操作。
// 取消节点获取资源的操作 private void cancelAcquire(Node node) { // 节点为null直接返回 if (node == null) return; // 置空节点持有的线程,因为此时节点线程已经发生中断 node.thread = null; Node pred = node.prev; // 这个循环是为了获取当前节点的上一个不为取消状态的节点,也就是中间如果发生了取消的节点都直接断开 while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 保存当前节点的上一个不为取消状态的节点的后继节点 Node predNext = pred.next; // 当前节点等待状态更新为CANCELLED node.waitStatus = Node.CANCELLED; // 如果当前节点为尾节点,则直接更新尾节点为当前节点的上一个不为取消状态的节点 if (node == tail && compareAndSetTail(node, pred)) { // 然后更新该节点的后继节点为null,因为它已经成为新的尾节点 pred.compareAndSetNext(predNext, null); } else { int ws; // 当前节点的上一个不为取消状态的节点已经不是头节点的情况,需要把当前取消的节点从AQS同步等待队列中断开 if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) pred.compareAndSetNext(predNext, next); } else { // 当前节点的上一个不为取消状态的节点已经是头节点,相当于头节点之后的节点都是取消,需要唤醒当前节点的后继节点 unparkSuccessor(node); } // 节点后继节点设置为自身,那么就不会影响后继节点 node.next = node; } }cancelAcquire()方法有多处调用,主要包括下面的情况:
1、节点线程在阻塞过程中主动中断的情况下会调用。
2、acquire的处理过程发生任何异常的情况下都会调用,包括tryAcquire()、tryAcquireShared()等。
3、新节点加入等待队列失败导致任何类型的异常或者带超时版本的API调用的时候剩余超时时间小于等于零的时候。
cancelAcquire()主要作用是把取消的节点移出同步等待队列,必须时候需要进行后继节点的唤醒。
实战篇