Question:AQS的等待队列初始化后,Head节点会一直是哨兵节点吗?
why is the question:
在AQS中,线程竞争锁失败后将被放入AQS实例的等待队列中。
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)) {
t.next = node;
return t;
}
}
}
}
image.png
image.png
第一次初始化等待队列,队列中队头为哨兵节点。而后,每次真正的线程节点都是拼接在尾部。
比如,在食堂等待打饭菜的队伍中,同学A开始找到了队尾开始入队,排在A前面的是同学B。当前关系是,A的前面是B,队尾不再是B,而是新入队的A。如果要满足双向链表定义,则B的后面是A。
当多个线程竞争同一个锁失败后,都进入了等待队列,队列的情况大概是这样的:
哨兵节点 ← node-1 ← node-2 ← node-3
tail节点是Node · 3,如果这时候当前持有锁线程释放锁,假设按照公平锁策略,node-1将要获取锁,可是我看到AQS中释放锁代码部分却是:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 将头部节点传入了unpark方法
return true;
}
return false;
}
释放锁的操作,选定了 head 节点作为下一个线程激活对象,可这head可不是哨兵节点嘛?哨兵节点里面并没有其代表的线程,源码中这么写是为什么?
try geting answer:
在前面写问题过程时,就突然想到了,head节点虽然哨兵节点,但是 head.next
不就是等待队列中最早等待的那个线程吗?
点开unparkSuccessor(h)
,这个方法里面激活的还真的是head.next
,说明前面的问题其实是肯定的,head节点,在队列初始化之后,一直都是哨兵节点。
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 实际激活的线程节点
Node s = node.next;
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)
LockSupport.unpark(s.thread);
}