AQS,全称AbstractQueuedSynchronizer
,是Concurrent包锁的核心,没有AQS就没有Java的Concurrent包。它到底是个什么,我们来看看源码的第一段注解是怎么说明
看完第一段,总结下
- AQS是一个同步的基础框架,基于一个先进先出的队列。
- 锁机制基于一个状态值,它是原子值。
- AQS的子类负责定义与操作这个状态值,但必须通过AQS提供的原子操作
- AQS剩余的方法就是围绕队列,与线程阻塞唤醒等功能
基于以上概念,我们看看源码到底是这么实现这些功能的
AQS的成员变量
state
private volatile int state;
该变量标记为volatile
,说明该变量是对所有线程可见的。作用在于每个线程改变该值,都会马上让其他线程可见,在CAS(可见锁概念与锁优化)的时候是必不可少的。在AQS类中,不会直接操作这个值,而是交由它的子类去操作和定义他的作用。
Node、head、tail
AQS中有一个静态内部类Node
,其实现是一个双向链表。head
与tail
则是这个链表的头尾指针。作用是存储获取锁失败的阻塞线程。同样的,这个链表是会被多个线程操作的,所以它里面的变量多是被标记为volatile
,并且操作也要通过CAS等原子方法去执行。
Node还有一个模式的属性:独占模式和共享模式。独占模式下,锁是线程独占的,而共享模式下,锁是可以被多个线程占用的。
VarHandler
对于大多数需要操作的原子属性,都对应会有一个大写的值,它的类是VarHandler
。例如state、head、tail
都有对应的VarHandler,STATE、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
再细了解。
总结
回顾下要点
- AQS是一个同步的基础框架,是ReentranLock、ReentranReadWriteLock的父类
- AQS原理是维护一个state原子值,通过一个双向链表的队列实现同步。
- 对于state、与队列的操作都是原子操作,通过VarHandle实现
- 主要对外方法是加锁与解锁,区别是否中断、超时、共享或独占模式
以上即使AQS的大致内容,可能有些部分难以理解,其实很正常,因为AQS提供的是流程模板与工具,没有实质落地的场景,是比较难理解的。等后面介绍ReentranLock
与ReentrantReadWriteLock
的时候,就可以更好更全面的了解整体AQS框架了。
如果觉得还不错,请关注微信公众号:Zack说码