为依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件、等等)提供一个实现的框架。此类的设计目标是为大多数依赖单个原子int值来表示状态的同步器提供一个有用的基础。子类必须定义改变状态的受保护的方法,定义何种状态对于此对象意味着被获取或被释放。有了这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用getState()、setState(int)和compareAndSetState(int, int)方法来操作以原子方式更新的int值。
子类应该被定义为非公共的内部帮助类,用来实现其封闭类的同步属性。AbstractQueuedSynchronizer没有实现任何同步接口。而是定义了诸如acquireInterruptibly(int)之类的方法,在适当的时候可以通过具体的锁和相关同步器来调用它们,以实现其公共方法。
此类支持默认的独占模式和共享模式之一,或者二者都支持。处于独占模式下时,其他线程将无法获取该锁。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不了解这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。处于不同模式下的等待线程可以共享相同的FIFO队列。通常,实现子类只支持其中一种模式,但两种模式都可以在ReadWriteLock中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法。
此类通过支持独占模式的子类定义了一个嵌套的ConditionObject类,可以将这个类用作Condition实现。isHeldExclusively()方法将报告同步对于当前线程是否是独占的;使用当前getState()值调用release(int)方法则可以完全释放此对象;如果给定保存的状态值,那么acquire(int)方法可以将此对象最终恢复为它以前获取的状态。没有别的AbstractQueuedSynchronizer方法创建这样的条件,因此,如果无法满足此约束,则不要使用它。AbstractQueuedSynchronizer.ConditionObject的行为取决于其同步器实现的语义。
此类为内部队列提供了检查、检测和监视方法,还为condition对象提供了类似方法。可以根据需要使用用于其同步机制的AbstractQueuedSynchronizer将这些方法导出到类中。
此类的序列化只存储维护状态的基础原子整数,因此已序列化的对象拥有空的线程队列。需要可序列化的典型子类将定义一个readObject方法,该方法在反序列化时将此对象恢复到某个已知初始状态。
使用
为了将此类用作同步器的基础,需要适当地重新定义以下方法,这是通过使用getState()、setState(int)和/或compareAndSetState(int, int)方法来检查和/或修改同步状态来实现的:
- tryAcquire(int)
- tryRelease(int)
- tryAcquireShared(int)
- tryReleaseShared(int)
- isHeldExclusively()
默认情况下,每个方法都抛出UnsupportedOperationException。这些方法的实现在内部必须是线程安全的,通常应该很短并且不被阻塞。定义这些方法是使用此类的唯一受支持的方式。其他所有方法都被声明为final,因为它们是无法各不相同的。
也可以查找从AbstractOwnableSynchronizer继承的方法,用于跟踪拥有独占同步器的线程。鼓励使用这些方法,这允许监控和诊断工具来帮助用户确定哪个线程保持锁。
即使此类基于内部的某个FIFO队列,它也无法强行实施FIFO获取策略。独占同步的核心采用以下形式:
Acquire:
while(!tryAcquire(arg)) {
enqueue thread if it is not already queued possibly block current thread
}
Release:
if (tryRelease(arg)) {
unblock the first queued thread
}
(共享模式与此类似,但可能涉及级联信号)
因为要在加入队列之前检查线程的获取状况,所以新获取的线程可能闯入其他被阻塞的和已加入队列的线程之前。不过如果需要,可以内部调用一个或多个检查方法,通过定义tryAcquire和/或tryAcquireShared来禁用闯入,提供一个公平的FIFO获取顺序。特别是,大多数公平的同步器可以定义tryAcquire返回false如果hasQueuePredecessors(设计用于在公平同步器中使用)返回true。其他变体也是可能的。
对于默认闯入(也称为greedy、renouncement、convoy-avoidance)策略,吞吐量和可伸缩性通常是最高的。尽管无法保证这是公平的和无偏向的,但允许更早加入队列的线程先于更迟加入队列的再次争用资源,并且相对于传入的线程,每个参与再争用的线程都有平等的成功机会。此外,尽管从一般意义上说,获取并非”自旋”,但他们可以在阻塞之前对用其他计算所使用的tryAcquire执行多次调用。在只保持独占同步时,这为自旋提供了最大的好处,但不是这种情况时,也不会带来最大的负担。如果需要这样做,可以使用”快速路径”检查来先行调用acquire方法,如果可能不需要争用同步器,则只能通过预先检查hasContended()和/或hasQueuedThreads()来确认这一点。
通过特殊化其同步器的使用范围,此类为部分同步化提供了一个有效且可伸缩的基础,同步器可以依赖于int型的state、acquire、release参数,以及内部一个FIFO等待队列。这些还不够的时候,可以使用atomic类、自己定制的Queue类和LockSupport阻塞支持,从更低级别构建同步器。
1 |
|