注:本文讨论关于Semaphore出现伪唤醒的场景分析,因为文章在书写时只摘取核心必要步骤论证,需要读者具备AQS源码无障碍阅读基础
初始时Semaphore=1
假设此时线程A直接拿到了该信号量,则Semaphore=0,然后线程A在互斥区执行很长时间
在线程A执行互斥区的时候,此时先后来了B、C两个线程,B、C线程入队,然后阻塞,此时队列状态如下图
此时A线程执行完互斥区,执行release操作,将头节点的waitStatus改为0,并唤醒线程B
线程B被唤醒后,由于信号量为1,可以抢到锁,会执行setHeadAndPropagate,此时Semaphore=0
会执行setHeadAndPropagate流程如下
然后走下面的逻辑
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
propagate=0 --> h != null --> h.waitStatus = 0 --> 拿到最新的头节点(h = head) != null
h.waitStatus =-1 < 0 符合
Node s = node.next; //s指向线程C对应的节点
s != null 而且s是Shared类型
所以线程B会执行doReleaseShared();
此时队列状态
unparkSuccessor操作:
信号量为0,但却去唤醒了线程C,出现了伪唤醒问题,但是由于C被唤醒后抢不到信号量,会接着阻塞,仍然保证了线程安全