关于 AQS 中PROPAGATE 状态

对于Semaphore来说,是有doReleaseShared并发和propagate=0的情况,比如在一个点成获取共享锁后,将头结点的状态置为0,propagate=0,但还未执行setHeadAndPropagate进行共享锁传播,此时资源为0,但此时有可能正在释放锁,发现已经将头结点的状态置为0了,此时会将头结点的状态置为 PROPAGATE 状态,让获取锁的线程任然能够进行共享锁传播,唤醒下一个线程。

=============
PROPAGATE的作用:就是更加快速、高效地唤醒后继节点。结构发生变化,就去唤醒,有不必要的唤醒。对于 PROPAGATE 状态,可以让刚刚获取到锁的线程,可以感知到其他线程并发释放资源,进而。调用 release 正常将head是0,并发 release会存在问题。

这本来是一个老版本JUC的BUG,后来通过引入PROGAGATE修复了它,概括讲就是在共享模式下,单纯靠tryAcquireShared()的返回值不足以判断是否需要传播唤醒(可能同时有多个acquire和release在进行),需要再引入一个状态。

https://www.zhihu.com/question/295925198?sort=created

=======

可以看到,早期版本的实现相比于现在的实现来说简单了很多,总结起来最主要的区别有以下几个:

在setHeadAndPropagate方法中,早期版本对节点waitStatus状态的判断只是!=0,而现在改为了<0;
早期版本的releaseShared方法中的执行逻辑和独占锁下的release方法是一样的,而现在将具体的唤醒逻辑写在了doReleaseShared方法里面,和setHeadAndPropagate方法共同调用。
https://www.cnblogs.com/tomakemyself/p/13928620.html

ReentrantReadWriteLock中PROPAGATE只是一个中间状态,共享锁的传播性由setHeadAndPropagate完成。对于有资源概念的Semaphore,PROPAGATE和setHeadAndPropagate组合完成共享锁的传播性。共享锁的传播性目的是尽快唤醒同步队列中等待的线程,使其尽快获取资源(锁),但是也有一定的副作用,可能会造成不必要的唤醒。
AQS的设计,尽快唤醒其他等待线程体现在3个地方:
共享锁的传播性。
doReleaseShared()中head改变,会循环唤醒head的后继节点。
线程获取锁失败后入队列并不会立刻阻塞,而是判断是否应该阻塞shouldParkAfterFailedAcquire,如果前继是head,会再给一次机会获取锁。
https://blog.csdn.net/weixin_36586120/article/details/108642253

至此我们知道了PROPAGATE的作用,就是为了避免线程无法会唤醒的窘境。,因为共享锁会有很多线程获取到锁或者释放锁,所以有些方法是并发执行的,就会产生很多中间状态,而PROPAGATE就是为了让这些中间状态不影响程序的正常运行。

https://blog.csdn.net/zy353003874/article/details/110535122

http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/locks/AbstractQueuedSynchronizer.java?r1=1.73&r2=1.74

posted @ 2021-08-25 17:19  TomStudio  阅读(648)  评论(1编辑  收藏  举报