应该将子类定义为非公共内部帮助器类,一般并发包类用内部类Sync sync来继承并实现。为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。子类必须定义重写此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用
getState()、setState(int) 和compareAndSetState(int, int)
方法来操作以原子方式更新的 int 值。
简单说来,AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,线程的显式阻塞是通过调用LockSupport.park()完成,而LockSupport.park()则调用 sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把 线程交给系统内核进行阻塞。
此类支持默认的独占
模式和共享
模式之一,或者二者都支持。处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不"了解"这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。处于不同模式下的等待线程可以共享相同的 FIFO 队列。通常,实现子类只支持其中一种模式,但两种模式都可以在(例如)ReadWriteLock
中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法。
此类通过支持独占模式的子类定义了一个嵌套的AbstractQueuedSynchronizer.ConditionObject
类,可以将这个类用作
Condition
实现。isHeldExclusively()
方法将报告同步对于当前线程是否是独占的;使用当前getState()
值调用
release(int)
方法则可以完全释放此对象;如果给定保存的状态值,那么
acquire(int)
方法可以将此对象最终恢复为它以前获取的状态。没有别的 AbstractQueuedSynchronizer 方法创建这样的条件,因此,如果无法满足此约束,则不要使用它。AbstractQueuedSynchronizer.ConditionObject
的行为当然取决于其同步器实现的语义。
此类的序列化只存储维护状态的基础原子整数,因此已序列化的对象拥有空的线程队列。需要可序列化的典型子类将定义一个 readObject 方法,该方法在反序列化时将此对象恢复到某个已知初始状态。
使用
为了将此类用作同步器的基础,需要适当地重新定义以下方法,这是通过使用getState()、setState(int)
和/或compareAndSetState(int, int)
方法来检查和/或修改同步状态来实现的:
Protected子类实现 |
解释 |
tryAcquire(int) |
独占模式获取。先尝试获取,如果不成功入队,等待其它线程release信号。用于Lock的tryLock实现。 |
tryRelease(int) |
独占模式释放状态 |
tryAcquireShared(int) |
共享模式获取尝试。先查询对象的状态是否允许共享,然后尝试获取。获取失败,入队等待其它线程释放信号。 |
tryReleaseShared(int) |
共享的模式下释放状态 |
Boolean isHeldExclusively() |
如果当前调用线程独占,返回true。 这个方法被调用,在每次调用非等待conditionobject方法。 |
默认情况下,每个方法都抛出UnsupportedOperationException。这些方法的实现在内部必须是线程安全的,通常应该很短并且不被阻塞。子类通过实现这些方法来实现不同业务,其他所有方法都被声明为 final。
开始提到同步器内部基于一个FIFO队列,对于一个独占锁的获取和释放有以下伪码可以表示。
AbstractOwnableSynchronizer的主要方法是set/get当前独占线程。
即使此类基于内部的某个 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;
(共享模式与此类似,但可能涉及级联信号。)
CLH锁对应CLH的详细介绍请参考此片论文。
等待队列内部节点的实现
分类 |
static final常量 |
解释 |
模式 Node mode |
SHARED = new Node(); |
标志当前线程为共享模式 |
EXCLUSIVE = null; |
独占模式 |
|
状态 Int waitStatus |
CANCELLED = 1; |
线程已经取消 |
SIGNAL = -1; |
等待唤醒,也就是unpark |
|
CONDITION = -2; |
等待条件执行,也就是在condition队列中 |
|
PROPAGATE = -3; |
表示当前场景下后续的acquireShared能够得以执行; |
|
0 |
以上数值均无数值排列以简化使用。非负值意味着节点不需要信号。因此,大多数代码不需要检查特定的值,只是为了签名。字段为正常同步节点初始化为0,条件节点的条件为。它被修改使用CAS(或在可能的情况下,无条件写立即可见)。 |
- SIGNAL :这个节点的继承人(或即将)阻塞(通过park),所以当前节点必须启动它的继任者时,它释放或取消。为了避免竞争,获取方法必须首先指示它们需要一个信号,然后重试原子获取,然后,在失败,阻塞。
- CANCELLED :此节点由于超时或中断而被取消。节点永远不会离开这个状态。特别是,取消节点的线程永远不会再次阻塞。条件:此节点当前处于条件队列中。它将不会被用作一个同步队列节点,直到被转移,此时状态将被设置为0。(这个值什么都不做但简化操作。)
- PROPAGATE :一个releaseshared传播应该传播到其他节点。这是一套(头节点)在doreleaseshared确保传播下去,即使其他业务已经介入。
Node prev
前驱节点,比如当前节点被取消,那就需要前驱节点和后继节点来完成连接。指向当前节点的前驱,依赖于检查WaitStatus(线程状态)。入队时指定,出队时指向null(方便GC)。此外,在取消一个前任,我们一直找一个非取消的,这肯定存在,因为头节点永远不会取消:只有成功获得的节点成为头节点。取消的线程将不会成功获取,线程仅取消自身,不影响其他节点。
Node next
后继:当前节点因release而被唤醒后,链接到当前节点的后继节点。在调整分配入队,当旁路取消前任和清零(为了GC)当出列。入队操作并不指定next,所以看到next空并不一定意味着节点在终结队列。可以通过从tail遍历前驱做双重检查。取消节点的下一个字段设置为指向本身而非空,make life easier for isOnSyncQueue。如下:
Node nextWaiter
存储condition队列中的后继节点。链接到下一个节点等待条件,或共享特殊值。因为只有在独占模式下访问条件队列时,在等待条件时我们只需要一个简单的链接队列来保持节点。然后他们被转移到队列重新获得。由于条件只能是互斥的,我们通过保存特殊值来表示共享模式。
Thread thread
入队列时的当前线程。mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
参数:参数arg的获得。这个值是给tryacquireshared但另有解释,可以代表任何你喜欢的东西。
- Propagation 被调用,或记录(如h.waitstatus之前 或sethead之后)由先前的操作 (注:这是用WaitStatus则标志检查因为PROPAGATE 状态可能过渡到SIGNAL。)
- 下一个节点在共享模式中等待,
或者我们不知道,因为它看起来是空的。
这两项检查中的保守主义可能会导致不必要的唤醒,但只有当有多个获得/释放,所以大多数需要信号,现在或很快无论如何。