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 框架了.
来源: https://www.cnblogs.com/zackku/p/9984887.html