一、AQS基本介绍
同步器AbstractQueuedSynchronizer(简称AQS)是用来构建其他同步组件的基础,它使用了一个int变量来表示同步状态state,通过内置的FIFO队列来完成线程的排队工作。
二、如何使用AQS来构建同步组件?
同步器的设计是基于模板模式的,使用者继承同步器并重写指定的方法。同步器可重写的方法如下:
方法名称 | 描述 |
---|---|
boolean tryAcquire(int arg) | 尝试独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态 |
boolean tryRelease(int arg) | 尝试独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态 |
int tryAcquireShared(int arg) | 尝试共享式获取同步状态,返回大于等于0的值,表示获取成功,反之获取失败。 |
boolean tryReleaseShared(int arg) | 尝试共享式释放同步状态 |
boolean isHeldExclusively() | 当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程独占 |
实现自定义同步组件时,将会调用同步器提供的模板方法,如下:
方法名称 | 描述 |
---|---|
void acquire(int arg) | 独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则将会进入同步队列等待,该方法会调用重写的tryAcquire方法 |
void acquireInterruptibly(int arg) | 与acquire相同,但是该方法响应中断。如果当前线程未获取到同步状态而进入同步队列,如果当前线程被中断,则该方法会抛出InterruptedException异常 |
boolean tryAcquireNanos(int arg, long nanos) | 在acquireInterruptibly基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,则返回false,反之返回true |
void acquireShared(int arg) | 共享式地获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式主要区别在于同一时刻可以有多个线程获取到同步状态 |
void acquireSharedInterruptibly(int arg) | 与acquireShared相同,该方法响应中断 |
boolean tryAcquiredSharedNanos(int arg, long nanos) | 在acquireSharedInterruptibly基础上增加了超时限制 |
boolean release(int arg) | 独占式的释放同步状态,该方法在释放同步状态之后,将同步队列的第一个节点的线程唤醒 |
boolean releaseShared(int arg) | 共享式的释放同步状态 |
Collection getQueuedThreads() | 获取等待在同步队列上的线程集合 |
三、AQS的实现分析
从实现角度来分析同步器是如何完成线程同步?
- 同步队列
- 独占式同步状态获取与释放
- 共享式同步状态获取与释放
- 超时获取同步状态
3.1 同步队列
AQS内部基于同步队列(一个双向队列)来完成同步状态的管理。当前线程获取同步状态失败时,会将当前线程以及等待状态等信息构造成为一个Node节点并将其加入同步队列,同时将当前线程挂起,当同步状态释放,会把首节点中的线程唤醒,使其再次获取同步状态。
Node类结构如下:
属性类型与名称 | 描述 |
---|---|
int waitStatus | 等待状态。1、CANCELLED,值为1,由于在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待,节点进入该状态将不会变化。2、SIGNAL,值为-1,后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或被取消,将会通知后继节点,使后继节点的线程得以运行。3、CONDITION,值为-2,节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中。4、PROPAGATE,值为-3,表示下一次共享式同步状态获取将会无条件的被传播下去。5、INITIAL,值为0,初始状态 |
Node prev | 前驱节点,当节点加入同步队列时被设置 |
Node next | 后继节点 |
Node nextWaiter | 等待队列(后续章节会介绍)中的后继节点。如果当前节点是共享的,那么这个字段是SHARED常量。也就是说节点类型(独占或共享)和等待队列中的后继节点共用同一个字段 |
Thread thread | 对应的线程 |
Node节点是构成同步队列的基础,没有成功获取同步状态的线程将会成为Node节点加入该队列的尾部,头节点是拥有同步状态的节点(如果是首次初始化,头节点会是空Node,这里只要记住头节点后面的节点都是在等待获取同步状态的节点)。同步队列的基本结构如下:
3.2 独占式获取、释放同步状态
获取同步状态:通过调用同步器的acquire(int arg)方法获取同步状态,该方法无法响应中断。下述代码完成了同步状态获取、节点构造、加入同步队列等相关工作。
public final void acquire(int arg) {
if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
流程如下:
- 调用自定义同步器的tryAcquire(int arg)方法,该方法保证线程安全的获取同步状态,如果获取成功,则设置独占线程为当前线程,如果获取失败,构造节点并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部。
- 接着调用acquireQueued(Node node, int arg)方法,使得节点再次尝试获取同步状态
- 如果获取不到会调用LockSupport.park()方法将当前线程挂起,而被挂起线程的唤醒主要依靠前驱节点的出队或线程被中断来实现。
释放同步状态:当前线程获取同步状态并执行了相应逻辑后,就需要释放同步状态,使得后续节点能够继续获取同步状态。通过调用同步器的release(int arg) 方法就可以释放同步状态,该方法在释放同步状态后,会唤醒其后继节点。
public final boolean release(int arg) {
if(tryRelease(arg)) {
Node h = head;
if(h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
总结:
- 获取同步状态时,同步器维护一个同步队列,获取状态失败的线程会被加入到队列尾部。这里会先判断一下前驱节点是否是头节点,如果是,则尝试获取同步状态,如果获取成功,直接将该节点设置为头节点,如果获取失败,将当前线程挂起。
- 释放同步状态时,同步器调用release(int arg)方法释放同步状态,唤醒头节点的后继节点。
3.3 共享式获取、释放同步状态
通过调用同步器的acquireShared(int arg) 方法可以共享式地获取同步状态。
public final void acquireShared(int arg) {
if(tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for(;;) {
final Node p = node.predecessor();
if(p == head) {
int r = tryAcquireShared(arg);
if(r >= 0) {
setHeadAndPropagate(node, r);
p.next = null;
if(interrupted)
selfInterrupt();
failed = false;
return;
}
}
if(shouldParkAfterFailedAcquire(p, node)
&& parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if(failed) {
cancelAcquire(node);
}
}
}
在acquireShared(int arg)方法中,同步器调用tryAcquireShared(int arg)方法尝试获取同步状态,方法返回值为int值,如果大于等于0,表示能够获取到同步状态,反之无法获取同步状态。在doAcquireShared(int arg)方法的自旋过程中,如果当前节点的前驱为头节点,尝试获取同步状态,如果返回值大于等于0,表示该次获取同步状态成功并从自旋过程中退出。
与独占式一样,共享式也需要释放同步状态,通过调用releaseShared(int arg)方法可以释放同步状态。它与独占式地主要区别在于 tryReealseShared(int arg)方法必须确保同步状态安全释放,一般是通过循环+CAS来保证的。
public final boolean releaseShared(int arg) {
if(tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
3.4 超时获取同步状态
通过调用同步器的doAcquireNanos(int arg, long nanosTimeout)方法可以超时获取同步状态,在指定的时间段内获取同步状态,如果获取到则返回true,否则返回false。
超时获取同步状态过程是响应中断获取同步状态过程的升级版,doAcquireNanos方法在支持响应中断的基础上,增加了超时获取的特性,该方法提供了synchronized关键字不具备的特性。
独占式超时获取同步状态和独占式获取同步状态在流程上非常类似,主要区别在于未获取到同步状态时的处理逻辑。acquire(int arg)在未获取到同步状态时,将会使当前线程一直处于等待状态,而doAcquireNanos()方法会使当前线程等待nanosTimeout纳秒,如果当前线程在nanosTimeout纳秒内没有获取到同步状态,将会直接返回false。
四、总结
自定义同步组件可通过继承AQS并实现它的抽象方法来管理同步状态。同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,来实现不同类型的同步组件(如ReentrantLock可重入锁、ReentrantReadWriteLock读写锁和CountDownLatch锁存器)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。