Semaphore的伪唤醒问题

18 阅读1分钟

注:本文讨论关于Semaphore出现伪唤醒的场景分析,因为文章在书写时只摘取核心必要步骤论证,需要读者具备AQS源码无障碍阅读基础

初始时Semaphore=1
假设此时线程A直接拿到了该信号量,则Semaphore=0,然后线程A在互斥区执行很长时间 在线程A执行互斥区的时候,此时先后来了B、C两个线程,B、C线程入队,然后阻塞,此时队列状态如下图

image.png 此时A线程执行完互斥区,执行release操作,将头节点的waitStatus改为0,并唤醒线程B

image.png

线程B被唤醒后,由于信号量为1,可以抢到锁,会执行setHeadAndPropagate,此时Semaphore=0

image.png

会执行setHeadAndPropagate流程如下

image.png

然后走下面的逻辑

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();

image.png

此时队列状态

image.png

unparkSuccessor操作:

image.png

信号量为0,但却去唤醒了线程C,出现了伪唤醒问题,但是由于C被唤醒后抢不到信号量,会接着阻塞,仍然保证了线程安全

image.png