为何AbstractQueuedSynchronizer的cancelAcquire()要将node.next设为自身?
node.next = node的疑问 我在阅读AbstractQueuedSynchronizer(AQS)源码时,对cancelAcquire()方法的最后一行代码感到困惑,完整方法代码如下:
private void cancelAcquire(Node node) { // Ignore if node doesn't exist if (node == null) return; node.thread = null; // Skip cancelled predecessors Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // predNext is the apparent node to unsplice. CASes below will // fail if not, in which case, we lost race vs another cancel // or signal, so no further action is necessary, although with // a possibility that a cancelled node may transiently remain // reachable. Node predNext = pred.next; // Can use unconditional write instead of CAS here. // After this atomic step, other Nodes can skip past us. // Before, we are free of interference from other threads. node.waitStatus = Node.CANCELLED; // If we are the tail, remove ourselves. if (node == tail && compareAndSetTail(node, pred)) { pred.compareAndSetNext(predNext, null); } else { // If successor needs signal, try to set pred's next-link // so it will get one. Otherwise wake it up to propagate. int ws; 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; // help GC !!!!CONFUSE ME!!!! } }
我不解的是,最后一行代码node.next = node; // help GC为什么不直接将node.next设为null?这样同样能帮助垃圾回收,希望有人能解答我的疑问。
解答
将node.next设为自身而非null,核心是为了避免队列遍历逻辑出现异常,同时确保GC能正常回收节点,具体原因如下:
避免遍历逻辑中断或空指针
AQS内部存在不少遍历队列的逻辑(比如寻找下一个需要唤醒的节点),这些逻辑会沿着节点的next指针遍历。如果将已取消节点的next设为null,遍历到这个节点时会直接终止,导致后续的有效节点无法被处理。而将next指向自身后,遍历逻辑可以通过判断next == node识别出这是一个已取消的节点,从而正确跳过它,继续处理后续节点。确保GC正常回收的同时避免残留引用问题
HotSpot虚拟机的垃圾回收器可以识别循环引用并回收对应的对象,所以node.next = node这种自引用不会阻止GC回收该节点。相反,如果设为null,若有其他线程临时持有该节点的引用,后续访问next时可能触发空指针异常;而自引用的方式可以避免这种情况,同时彻底切断该节点与队列有效链的联系。符合AQS的节点状态约定
AQS中已取消的节点需要被彻底从队列的有效链路中移除,将next指向自身是一种明确的标记,告诉其他线程该节点已经作废,无需再处理它的后续节点,这是AQS内部统一的节点作废标记方式。
内容的提问来源于stack exchange,提问作者Tom Chen

