AQS 技术
1. AQS 概述
1.1 管程
管程是由局部于自己的若干公共变量及其说明和所有访问这些公共变量的过程所组成的软件模块。
1.2 Java 中管程的实现
Java通过管程的方式自行解决线程互斥和同步的问题,Object Monitor模式就是一种管程的实现。Java中另一种管程的实现是AQS(Abstract Queued Synchronizer,抽象队列同步器)技术。两者区别如下:
- 实现原理不同。
- Object Monitor它处于JVM层面,程序员不能根据自己的业务形态基于管程原理扩展新的功能。使用AQS技术实现的管程处于SDK层面,基于这种管程的控制思路,对控制功能进行扩展,从而实现自身业务所需的控制功能。
2. AQS 技术的基本原理
2.1 AQS 技术的工作过程
AQS 需要解决两个问题:
- 两个或多个线程的互斥和同步问题,只有一个线程可以运行,其他线程进行阻塞等待。
- 通知线程间的状态变化。
2.2 AQS 工作模式
2.2.1 独占模式(Exclusive)
独占模式是指CLH队列中能够在同一时间获取资源操作权的申请者最多只有一个,如果CLH队列以独占模式工作,那么队列中的Node节点为ExclusiveNode节点。
2.2.2 共享模式(Share)
同时可以被多个线程获取,具体的资源个数可以通过参数指定。如CountDownLatch。
2.3 AQS方法
AQS内部使用了一个volatile的变量state来作为资源的标识。同时定义了几个获取和改版state的protected方法,子类可以覆盖这些方法来实现自己的逻辑。
- getState():得到加锁状态。
- setState():设置加锁状态。
- compareAndSetState(): cas 方法设置锁。
AQS的设计是基于模板方法模式的,它有一些方法必须要子类去实现的:
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
模板方法:是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。https://refactoringguru.cn/design-patterns/template-method
2.4 AQS之Node节点
每个等待中的线程都是生成一个节点,放入到双向等待队列中。Node节点属性如下:
- prev:前置节点。
- next:后置节点。
- thread:线程对象。
- nextWaiter:等待队列里下一个等待条件的结点,实现Condition条件上的等待线程队列(单向队列),这个Condition主要用在ReentrantLock类中。
- waitStatus:记录当前节点状态。
2.5 AQS 数据结构
AQS本质上是一种管程实现,其内部有一个双端队列,称为CLH队列(CLH队列以其三位主要提出人Craig、Landin、Hagersten命名)。这个队列的主要特点是:
- 利用双向链表的操作特点(FIFO,先入先出)保证资源操作权分配的公平性。
- 利用双向链表的结构特点保证各节点间的操作状态可知。
- 利用Node节点所代表的线程的自旋操作方式提高资源操作权获取性能。
2.6 CLH 工作特点
所有状态正常的节点只能由队列头部离开队列。但是当CLH队列主动清理状态不正常的节点(CANCELLED状态的节点)时,这些状态不正常的节点不一定从队列头部离开队列。
- 所有状态正常的节点只能由队列头部离开队列。但是当CLH队列主动清理状态不正常的节点(CANCELLED状态的节点)时,这些状态不正常的节点不一定从队列头部离开队列。
- CLH队列只能从尾部添加节点。由于存在多个线程同时要求添加节点的场景,因此CLH队列要解决的一个关键问题是如何从尾部正确添加节点。
- CLH队列在初始化完成的瞬间,其头节点引用head和尾节点引用tail指向同一个节点。也就是说,真正被阻塞等待获取资源操作权的节点,至少是从CLH队列的第二个节点开始的,而不是头节点开始的。头节点引用head要么代表当前已经获取资源操作权的节点,要么没有实际意义。
- 在申请资源操作权时,只有没有立即申请到资源操作权的线程,才会有对应的Node节点进入CLH队列,立即申请到资源操作权的线程,不会有对应的Node节点进入CLH队列,甚至在申请过程中都不会产生对应的Node节点(没有申请到资源操作权的线程,会先进行自旋操作,再进行CLH队列的添加操作)。