细节爆炸!并发编程的半壁江山——AQS详解( 三 )
文章插图
结点状态waitStatus,需要保证可见性,用volatile修饰 这里我们说下Node 。 Node结点是对每一个等待获取资源的线程的封装 , 其包含了需要同步的线程本身及其等待状态 , 如是否被阻塞、是否等待唤醒、是否已经被取消等 。 变量waitStatus则表示当前Node结点的等待状态 , 共有5种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE、0 。
- CANCELLED(1):表示当前结点已取消调度 。 当timeout或被中断(响应中断的情况下) , 会触发变更为此状态 , 进入该状态后的结点将不会再变化 。
- SIGNAL(-1):表示后继结点在等待当前结点唤醒 。 后继结点入队时 , 会将前继结点的状态更新为SIGNAL 。
- CONDITION(-2):表示结点等待在Condition上 , 当其他线程调用了Condition的signal()方法后 , CONDITION状态的结点将从等待队列转移到同步队列中 , 等待获取同步锁 。
- PROPAGATE(-3):共享模式下 , 前继结点不仅会唤醒其后继结点 , 同时也可能会唤醒后继的后继结点 。
- 0:新结点入队时的默认状态 。
同步队列中节点的增加和移除
文章插图
通过图可以看出来,在增加 尾节点的时候需要通过CAS设置,因为可能是多个线程同时设置,但是移除首节点的时候是不需要的,因为这个操作是由同步器操作的,并且首节点只有一个
独占式同步状态的获取与释放
文章插图
AQS的从线程的获取同步状态到,对同步队列的维护,到释放,的流程图就是这样的,有兴趣看源码的自己去跟一下,就是主要实现的模板方法,
注意:其实在这个给大家提个醒,看源码的时候,找核心的看,找主要的看,不要一行一行地扣着看,没有意义,还有就是调用过程复杂,体会核心流程就可以
之前写了<
Condition实现分析:
文章插图
一个锁是可以有多个Condition的,每个Condition都包含一个自己的等待队列,不同于Object属于同一个对象等待,他存在一个单链表结构的等待队列,清晰的知道要唤醒自己的等待队列中的节点,所以采用signal方法而不是signalall
当然采用的类还是Node类当然单链表其实就是没有上一个节点的引用而已
等待队列和同步队列采用的是相同的类,只不过是实现的数据机构确是不一样的而已
最终一个锁的实例化会成为上图中第二个图的这种形式,Demo也就是之前写的<
节点在队列中的移动
文章插图
就是在当前线程await的时候从同步队列移除后加入到等待队列尾部,而唤醒就是从等待队列移除后加入到同步队列尾部,两个队列相互转换的过程,之所以采用同一个类,就是为了方便的在不同队列中相互转化
当然这也是为什么不推荐使用SignalAll方法的原因,因为如果一个等待队列中有很多的线程在等待,全部唤醒后,最多且只能有一个线程获取到同步状态,其他线程全部要被加入到同步队列的末尾,而且也可能当前的同步状态被别人持有,一个线程也获取不到,全部都要被加入同步队列中,所以不推荐使用SignalAll,推荐是用Signal
其实也可以想象,比如wait和notify/notifyAll 在写<<线程之间的协作(等待通知模式)>>这篇文章的时候的最后一个问题也可以大概想象一下,应该也是维持了一个同步队列,但是等待队列应该是只有一个,所以,被唤醒的是第一个等待的节点,但是它没有办法保证要被唤醒的节点一定是在头一个,只能唤醒全部的节点,来保证需要唤醒的线程一定被唤醒,大概也是这样的一个节点的移动,根据网络文章的描述,应该八九不离十
根据猜测,结合上方的Condition接口分析,所以说,在wait,notify/notifyAll中推荐使用notifyAll,防止第一个节点不是需要唤醒的节点,造成唤醒错误,但是Condition是知道的,被唤醒的一定是需要唤醒的,不会唤醒错误,所以说,推荐使用signal
- 高像素|加持高像素只为解析力?vivo S7丛林秘境展对样张细节的要求更严苛
- 用心|用心网友制作了一加9 Pro渲染图:细节程度堪比官方
- 选对|为何都说这次OriginOS的方向选对了?来看下这些细节就知道了
- Store|苹果将在韩国开设第二家Apple Store直营店 并发布纪念壁
- 刘作虎|一加9系列更多细节曝光,刘作虎也玩起了“三机齐发”
- Linux(服务器编程):百万并发服务器系统参数调优
- 参数|外行看参数内行看细节,新手能牢记这四点,你也算半个内行了
- 并发容器ConcurrentHashMap
- 华为Mate40系列细节曝光,90Hz高刷+红外测温
- 为什么 Redis 单线程能支撑高并发?