把节点加入到条件队列中以后,接下来要做的就是释放锁资源:
//入参就是新创建的节点,即当前节点 final int fullyRelease(Node node) { boolean failed = true; try { //这里这个取值要注意,获取当前的state并释放,这从另一个角度说明必须是独占锁 //可以考虑下这个逻辑放在共享锁下面会发生什么? int savedState = getState(); //跟独占锁释放锁资源一样,不赘述 if (release(savedState)) { failed = false; return savedState; } else { //如果这里释放失败,则抛出异常 throw new IllegalMonitorStateException(); } } finally { //如果释放锁失败,则把节点取消,由这里就能看出来上面添加节点的逻辑中只需要判断最后一个节点是否被取消就可以了 if (failed) node.waitStatus = Node.CANCELLED; } }走到这一步,节点也加入条件队列中了,锁资源也释放了,接下来就该挂起了(先忽略中断处理,单看挂起逻辑):
//如果不在同步队列就继续挂起(signal操作会把节点加入同步队列) while (!isOnSyncQueue(node)) { LockSupport.park(this); //中断处理后面再分析 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } //判断节点是否在同步队列中 final boolean isOnSyncQueue(Node node) { //快速判断1:节点状态或者节点没有前置节点 //注:同步队列是有头节点的,而条件队列没有 if (node.waitStatus == Node.CONDITION || node.prev == null) return false; //快速判断2:next字段只有同步队列才会使用,条件队列中使用的是nextWaiter字段 if (node.next != null) return true; //上面如果无法判断则进入复杂判断 return findNodeFromTail(node); } //注意这里用的是tail,这是因为条件队列中的节点是被加入到同步队列尾部,这样查找更快 //从同步队列尾节点开始向前查找当前节点,如果找到则说明在,否则不在 private boolean findNodeFromTail(Node node) { Node t = tail; for (;;) { if (t == node) return true; if (t == null) return false; t = t.prev; } }如果被唤醒且已经被转移到了同步队列,则会执行与独占锁一样的方法acquireQueued()进行同步队列独占获取。
最后我们来梳理一下里面的中断逻辑以及收尾工作的代码:
在把唤醒后的中断判断做好以后,看await()中最后一段逻辑:
//在处理中断之前首先要做的是从同步队列中成功获取锁资源 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; //由于当前节点可能是由于中断修改了节点状态,所以如果有后继节点则执行删除已取消节点的操作 //如果没有后继节点,根据上面的分析在后继节点加入的时候会进行删除 if (node.nextWaiter != null) unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); //根据中断时机选择抛出异常或者设置线程中断状态 private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { if (interruptMode == THROW_IE) throw new InterruptedException(); else if (interruptMode == REINTERRUPT) //实现代码为:Thread.currentThread().interrupt(); selfInterrupt(); }