【Java并发】AQS一:AbstractQueuedSynchronizer同步工具介绍

AQS提供一个框架来实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件等)。该类被设计为大多数类型的同步器的有用基础,这些同步器依赖于单个原子 state 值来表示状态。子类必须定义更改此状态的受保护方法,并定义该状态对于正在获取或释放的对象的含义。鉴于此,该类中的其他方法执行所有排队和阻塞机制。子类可以维护其他状态字段,但是只跟踪使用方法getState()、setState()和compareAndSetState()来 自动更新的 state 值。

AQS的子类应该定义为非公共内部帮助器类,用于实现其封闭类的同步属性。类AbstractQueuedSynchronizer不实现任何同步接口。相反,它定义了acquireInterruptibly()等方法,这些方法可以被具体的锁和相关的同步器适当地调用,以实现它们的公共方法。

AQS类支持默认的 独占(排他)模式 和 共享模式。当以独占模式获取时,其他线程尝试获取的操作无法成功。由多个线程获得的共享模式可能(但不一定)成功。这种方式不难理解。除了在机械意义上的差异之外,当共享模式获取成功时,下一个等待的线程(如果存在)也必须确定它是否也可以获取。在不同模式下等待的 线程共享相同的FIFO队列。通常,实现子类只支持其中一种模式,单独实现共享模式或者单独实现独占模式,但是这两种模式都可以发挥作用,例如在ReadWriteLock中。只支持独占模式或只支持共享模式的子类不需要定义支持未使用模式的方法。

这个类定义了一个嵌套的ConditionObject类,它实现Condition并支持独占模式的方法isHeldExclusively() 确定同步是否被当前线程持有,调用方法release() 与当前getState() 值来完全释放这个对象,通过acquire()方法保存这个状态值,最终将此对象恢复到其先前获得的状态。否则没有AbstractQueuedSynchronizer方法创建这样的条件,所以如果不能满足这个约束,就不要使用它。ConditionObject的行为当然取决于它的AQS实现类即同步器实现的语义。

AQS类提供内部队列的检查、检测和监视方法,以及条件对象的类似方法。可以根据需要使用AbstractQueuedSynchronizer将它们导出到类中,用于它们的同步机制。

该类的序列化只存储底层原子整数维护状态,因此反序列化对象具有空线程队列。需要序列化的典型子类将定义一个readObject()方法,该方法在反序列化时将其恢复到已知的初始状态。

要使用该类作为同步器的基础,请根据需要重新定义以下方法:使用getState()、setState()(或者compareAndSetState())检查和修改同步状态

tryAcquire():独占方式获取锁
tryRelease():独占方式释放锁
tryAcquireShared:共享方式获得锁
tryReleaseShared:共享方式释放锁
isHeldExclusively:如果同步仅针对当前(调用)线程执行,则返回true。此方法在每次调用非等待的ConditionObject方法时调用,
如果不使用ConditionObject不需要实现,可通过realase()实现

默认情况下,这些方法中的每一个都会抛出UnsupportedOperationException。这些方法的实现必须是内部线程安全的,并且通常应该是简短的,而不是阻塞的。定义这些方法是 只支持 使用该类的方法。所有其他方法都声明为 final,因为它们不能独立更改。

AQS继承抽象方法AbstractOwnableSynchronizer,这个类对于跟踪拥有独占同步器的线程非常有用,这使监视和诊断工具能够帮助用户确定哪些线程持有锁。

即使这个类基于内部FIFO队列,它也不会自动执行FIFO获取策略。互斥同步的核心形式为:
Acquire:

while (!tryAcquire(arg)) {
         未排队的线程进入队列;
         可能阻塞当前线程;
      }

Release:

 if (tryRelease(arg))
         解除第一个排队线程的阻塞;

共享模式类似,但可能涉及级联信号。

因为检查获取是在排队之前调用的,所以一个新获取的线程可能会在其他被阻塞和排队的线程之前闯入。但是,如果需要,您可以定义tryAcquire()或tryAcquireShared(),通过内部调用一个或多个检查方法来禁用闯入的线程,从而提供一个公平的 FIFO获取命令。特别是,大多数公平同步器可以定义tryAcquire() 返回false, 如果有hasQueuedPredecessors()返回true, 其他变化也是有可能的。

吞吐量和可伸缩性通常在默认的barging(也称为贪心、放弃声明和 convoey -avoidance)策略中最高。
虽然不能保证这是公平的或无饥饿的,但是允许较早的队列线程在较晚的队列线程之前重新争用,并且每次重新争用都有无偏倚的机会对传入的线程成功争用。
而且,当获得的时候不要“自旋spin”。在通常意义上,它们可以执行多次调用{@code tryAcquire},并在阻塞之前穿插其他计算。当独占同步只被短暂地持有时,
这就提供了自旋的大部分好处,而当独占同步不被短暂持有时,则无需承担大部分责任。如果需要,您可以通过前面的调用来增强这一点,以获得带有“fast-path”
检查的方法,可能需要预先检查{@link #hasContended}和/或{@link #hasQueuedThreads},只有在同步器可能不存在争用时才这样做。

这个类提供了一个高效的、可伸缩的同步基础,部分原因是通过将它的使用范围专门化到可以依赖于state、获取和释放参数以及内部FIFO等待队列的同步器。
当这还不够时,您可以使用{@link java.util.concurrent从较低的级别构建同步器。类,您自己的自定义队列类和LockSupport阻塞支持。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页