AQS 首节点为什么为null的虚节点
SunRan

一句话总结

节点入队不是原子操作!!!

等待队列正在有线程进行初始化,但只是进行到了Tail指向Head,没有将Head指向Tail,此时队列中有元素,需要返回True。如果Head没有指向Tail,这种情况下也需要将相关线程加入队列中。所以这块代码是为了解决极端情况下的并发问题。

具体代码

一切要先从 tryAcquire开始

1
2
3
4
5
6
7
8
9
10
11
12
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
......
}

尝试获取锁的时候如果 State=0(没有线程持有),会将锁的持有者设置为当前线程

考虑到并发的问题,并没有因为状态为0就直接 setExclusiveOwnerThread(current),而是加了一个 hasQueuedPredecessors的判断

s = h.next

1
2
3
4
5
6
7
8
9
10
11
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
// 如果在当前线程之前有一个排队的线程,则为true;
// 如果当前线程位于队列的头部或队列为空,则为false

如上是查询是否有线程等待获取的时间超过当前线程

如果说此时没有人获取锁,正常来说头和尾都是待初始化阶段都为null,那什么情况下才有可能 h != t

h != t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;//这里只是执行一个快速操作,它和enq里的else分支的逻辑一样
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);//如果上面快速操作没有成功,再执行enq
return node;
}

private Node enq(final Node node) {
for (;;) {//使用for循环,保证入队成功
Node t = tail;
if (t == null) { // 第一次入队,没有dummy node的存在,需先创建它
if (compareAndSetHead(new Node()))
tail = head;
} else { // 至少有一个node,尝试入队
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

在添加新的节点时,如果 tail == null会执行 enq方法,通过死循环保证入列的成功。

tail == null 表示节点还没有初始化需要创建。

先通过 CAS方式将 Head设置为 new Node(),设置成功再设置尾指针。

这个操作肯定不是原子性的,无法保证不被打断。所以 h != t的情况就是发生在 compareAndSetHead(new Node())tail = head的间隙中。

  • 本文标题:AQS 首节点为什么为null的虚节点
  • 本文作者:SunRan
  • 创建时间:2022-01-06 15:51:35
  • 本文链接:https://lksun.cn/2022/01/06/AQS-首节点为什么为null的虚节点/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论