https://blog.csdn.net/weixin_38038479/article/details/111915280
在阅读AQS源码的过程中,也许会存在这样的困惑,为什么当next指针对应的节点为null 或者取消时,从tail 向前遍历寻找最近的一个非取消的节点;
当前任释放时,需要获取继任者;AQS的实现方式是从tail 向前遍历,之所以这样是与入队时的逻辑有关;
见注释
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
// 假设线程1执行到这里被挂起,此时 next 指针还没有关联到,后新来的线程 n 可能已经被排列到后面去了,所以当 t 被需要时,它的 next 指针还没有设置或者重置;故需要从后到前寻找,而如果找寻下一个,而这个可能被遗漏了;
// 故api文档中有这样一说 (Or, said differently, the next-links are an optimization so that we don't usually need a backward scan.)
t.next = node;
return t;
}
}
}
}
源码内部类Node上的注释中有对这个场景的完整描述:
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* 节点的前置前置节点可能为非空,但是尚未在同步队列中,
* the CAS to place it on queue can fail. So we have to
* 因为cas 入队时可能失败。
* traverse from tail to make sure it actually made it. It
* 所以我们不得不后序遍历以确保正确的发现它。
* will always be near the tail in calls to this method, and
* 它总是在靠近尾节点,当执行这个方法时
* unless the CAS failed (which is unlikely), it will be
* 除非cas 失败(这不太可能),所以我们不会遍历太多
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
另一方面如果下一个节点时null(已经被GC ),如何找到下一个有效节点,也只能从后往前找了