多线程-AQS

AQS(AbstractQueuedSynchronizer):字面意思我们可以理解为抽象的队列同步器

大致解读:假如有5个线程同时要去获取这把锁,但同一时间只能有一条线程获取到我们的锁,获取失败的线程,也就是剩余的线程都会被打包成一个node节点(后面有node节点的解读)从尾部放如队列,以队列的形式等待上一节点的线程释放锁通知下一节点拿锁;

大致变化如下:

获取失败的线程会以CAS的形式设置到尾节点,图解如下

如果还理解不了可以看源码,多看几次来加深理解

释放锁后会将头节点从我们队列中去除,将next节点设为我们的新的头节点,大致表示如下

大致流程我们可以用一个图表示,如下

 

AQS中的数据结构-节点和同步队列

竞争失败的线程会打包成Node放到同步队列,Node可能的状态里:

CANCELLED线程等待超时或者被中断了,需要从队列中移走

SIGNAL后续的节点等待状态,当前节点,通知后面的节点去运行

CONDITION :  当前节点处于等待队列

PROPAGATE共享,表示状态要往后面的节点传播

0:表示初始状态

而这些状态会赋值给我们Node节点里面的waitStatus

分别表示我们当前节点的上一节点和下一节点

用来将获取锁失败的线程的当前信息保存起来的容器

指的是等待队列的下一个队列是谁,其实AQS里面还有一个等待队列的机制,大致如下

我们可以联系到之前写的这篇博客的等待通知机制的相关实现https://blog.csdn.net/qq_42709262/article/details/88982865

只有获取到了我们的同步队列中的锁后,才能进入等待队列同时释放我们的锁

而等待队列的节点与通知队列的节点是同一个数据结构,但他是单向链表,同时Condition也会指向节点的头部,和尾部调用Condition的await()方法时,流程如下:

调用signal()方法,会将node从我们的等待队列放回我们的同步队列去重新尝试获取锁,图解:

AQS作用:显示锁、读写锁、或者CounDownLatch等等锁和工具,都是基于AQS来实现的,它内部使用了先进先出的队列来完成获取线程的排队工作,这个类的开发者,也就是Java并发包大师(Doug Lea),他当时做AQS的初心就是希望它能够成为大部分同步器的基础。

我们可以看出,我们的显示锁,可重入锁、读写锁,CountDownLatch以及我们的线程池等,都是基于AQS实现的,其实我们的FutureTask,在1.7以前也是基于我们的AQS实现的,大家可以去看源码的下方注释,有提到

我们可以根据AQS来实现自己的同步工具

它的实现是基于模板模式来实现的,大家可以自己打开源码看下列方法:

独占式获取:accquire、acquireInterruptibly、tryAcquireNanos

共享式获取:acquireShared、acquireSharedInterruptibly、tryAcquireSharedNanos

独占式释放锁:release

共享式释放锁:releaseShared

需要子类覆盖的流程方法

独占式获取  tryAcquire

独占式释放  tryRelease

共享式获取 tryAcquireShared

共享式释放  tryReleaseShared

并不是子类覆盖了以上方法就可以表示是这就是独占锁了,还需设置这个同步器是否处于独占模式  isHeldExclusively

同步状态state:(为了标识我们这个锁已经获得)

getState:获取当前的同步状态

setState:设置当前同步状态

compareAndSetState 使用CAS设置状态,保证状态设置的原子性

具体方法细节,大家可以看AQS的方法描述,这里我就不一一列举了

猜你喜欢

转载自blog.csdn.net/qq_42709262/article/details/88993630