AQS.unparkSuccessor
释放锁成功之后,接下来要做的就是唤醒后面的进程,这个方法是在 AQS 中实现的
主要逻辑是:
- 获取当前节点状态,如果小于 0 ,则置为 0
- 获取当前节点的下一个节点,如果不为空,直接唤醒
- 如果为空,或者节点状态大于 0 ,则寻找下一个状态小于 0 的节点
代码的具体实现
private void unparkSuccessor(Node node) { // 获取当前节点的状态 int ws = node.waitStatus; // 如果节点状态小于 0 ,则进行 CAS 操作设置为 0 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 获取当前节点的下一个节点 s Node s = node.next; // 如果 s 为空,则从尾部节点开始,或者s.waitStatus 大于 0 ,说明节点被取消 // 从尾节点开始,寻找到距离 head 节点最近的一个 waitStatus <= 0 的节点 if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) // next 节点不为空,直接唤醒即可 LockSupport.unpark(s.thread); }
为什么要从尾节点开始寻找距离 head 节点最近的一个 waitStatus <= 0 的节点呢?
这是因为在 enq() 构建节点的方法中,最后是 t.next = node (忘了就再往上翻翻看),设置原来的 tail 的 next 节点指向新的节点
如果在 CAS 操作之后, t.next = node 操作之前,有其他线程调用 unlock 方法从 head 开始向后遍历,因为此时 t.next = node 还没有执行结束,意味着链表的关系还没有建立好,这样就会导致遍历的时候到 t 节点这里发生中断,因为此时 tail 还没有指向新的尾节点
如果从后向前遍历的话,就不会存在这样的问题
接下来下一个线程就被唤醒了,然后程序会把它当成新的节点开始执行
而原来执行结束的线程,则会将它从队列中移除,然后开始循环循环
这篇文章终于讲完了,阿粉的头发都快秃了
