为了账号安全,请及时绑定邮箱和手机立即绑定

Java多线程——AQS框架源码阅读

AQS全称AbstractQueuedSynchronizer是Concurrent包锁的核心没有AQS就没有Java的Concurrent包。它到底是个什么我们来看看源码的第一段注解是怎么说明



看完第一段总结下

  • AQS是一个同步的基础框架基于一个先进先出的队列。
  • 锁机制基于一个状态值它是原子值。
  • AQS的子类负责定义与操作这个状态值但必须通过AQS提供的原子操作
  • AQS剩余的方法就是围绕队列与线程阻塞唤醒等功能

基于以上概念我们看看源码到底是这么实现这些功能的

AQS的成员变量

state

private volatile int state;
该变量标记为volatile说明该变量是对所有线程可见的。作用在于每个线程改变该值都会马上让其他线程可见在CAS可见锁概念与锁优化的时候是必不可少的。在AQS类中不会直接操作这个值而是交由它的子类去操作和定义他的作用。

Node、head、tail

AQS中有一个静态内部类Node其实现是一个双向链表。headtail则是这个链表的头尾指针。作用是存储获取锁失败的阻塞线程。同样的这个链表是会被多个线程操作的所以它里面的变量多是被标记为volatile并且操作也要通过CAS等原子方法去执行。
Node还有一个模式的属性独占模式共享模式。独占模式下锁是线程独占的而共享模式下锁是可以被多个线程占用的。

VarHandler

对于大多数需要操作的原子属性都对应会有一个大写的值它的类是VarHandler。例如state、head、tail都有对应的VarHandlerSTATE、HEAD、TAIL。VarHandler是1.9的新特性提供了类似于原子操作以及Unsafe操作的功能里面的原子操作大多是native方法比较难查看源码。

ConditionObject

条件队列是AQS中一个非常关键内部类。这个名字起非常奇异让人搞不懂看它类注释也看不懂说了什么。看看AQS头部注解

这个类是为了让子类支持独占模式的。深入看其中的源码实现其实就是Node在功能性上的封装最终让子类实现让当前线程怎么独占一个Object锁。await()、dosign()等方法就是让线程阻塞、加入队列、唤醒线程等。AQS框架下基本各种独占的加锁解锁等操作到最后都是基于这个类实现的。该类是提供给子类去使用的具体实现等下次说ReentranLock再深入了解。有人可能觉得为什么实现这个内部类又不用而是给子类去用那为什么不放到子类去呢其实答案很简单抽象加模板模式

p.s. 只有独占锁才能配合该类使用。

AQS的成员函数

AQS的公用的方法主要是加锁与解锁方法。以下方法只提供了模板部分实现还是在子类当中直接调用会抛出异常。

acquire()

尝试获取锁失败则进入队列。

先执行tryAcquire()子类实现成功则直接返回如果是获取锁失败则执行addWaiter()通过CAS在双向链表的尾部添加一个新独占节点。

然后把节点丢到acquireQueued()中执行。该方法其实就是自旋尝试获取锁或阻塞线程子类实现决定。一开始获取新节点的前驱节点如果这个节点是head则证明只有两个节点此时再次执行tryAcquire()尝试获取锁若获取成功则不需要中断成功结束。

如果还是获取失败则执行shouldParkAfterFailedAcquire()根据前驱节点状态子类设值判断是否继续自旋当waitStatus为初始值重复上一步直到前面的节点一直在减少到前驱节点为head或者阻塞线程当waitStatus标记为SIGNAL

最后如果acquireQueued()返回需要阻塞则执行selfInterrupt()设置线程为中断

可以看回acquire()函数的写法十分的艺术。利用条件判断的短路规则实现在if()条件内嵌套判断执行语音。一般人(笔者本人)如果要实现这个功能会这么写

所以下次遇到类似嵌套if条件判断的语句可以学习下acquire()的这种短路写法。赞?

acquireInterruptibly()

检查线程是否被中断并尝试获取锁失败则进入队列。线程中断会退出队列。
流程基本和acquire()相同。不同点就是acquireInterruptibly()在自旋获取过程中如果线程是中断的那么就会抛出异常退出流程并且放弃锁。

doAcquireInterruptibly()方法与acquireQueued()方法非常相似不同就是前者在中断状态下不会再继续获取锁。注意最后有cancelAcquire()方法的执行。

tryAcquireNanos()

尝试获取锁失败则进入队列。当超过指定时间或线程中断会退出队列。
acquireInterruptibly()基础上增加多一个时间判断超过指定时间则退出放弃获取锁。

release()

释放当前锁并唤醒下一个Node。
尝试释放锁

若释放成功且waitStatus不为0证明是SIGNAL的就会执行unparkSuccessor()先取消SIGNAL标志然后找到最近一个需要SIGNAL的节点并且唤醒它。

**shared()

以上方法皆为独占模式对应都有共享模式的方法。最大的不同其实就是Node的waitStatus值为PROPAGATE。具体流程与独占大体相同细节留到ReentrantReadWriteLock再细了解。

总结

回顾下要点

  1. AQS是一个同步的基础框架是ReentranLock、ReentranReadWriteLock的父类
  2. AQS原理是维护一个state原子值通过一个双向链表的队列实现同步。
  3. 对于state、与队列的操作都是原子操作通过VarHandle实现
  4. 主要对外方法是加锁与解锁区别是否中断、超时、共享或独占模式

以上即使AQS的大致内容可能有些部分难以理解其实很正常因为AQS提供的是流程模板与工具没有实质落地的场景是比较难理解的。等后面介绍ReentranLockReentrantReadWriteLock的时候就可以更好更全面的了解整体AQS框架了。


点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
0
获赞与收藏
2

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消