Java 队列同步器 AQS
本文部分摘自《Java 并发编程的艺术》
概述
队列同步器 AbstractQueuedSynchronize(以下简称同步器),是用来构建锁(Lock)或者其他同步组件(JUC 并发包)的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置的 FIFO 队列来完成资源获取线程的排队工作
同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,子类推荐被定义为自定义同步组件的静态内部类。同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态的获取和释放方法来供自定义组件使用
一言以蔽之,同步器是实现锁(也可以是任意同步组件)的一种方式,它屏蔽了更加底层的一些机制,使开发者更易于理解和使用
队列同步器的接口
同步器的设计是基于模板方法模式的,使用者需要继承队列同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法
1. 访问或修改同步状态
重写同步器指定的方法时,需要使用同步器提供的如下三个方法来访问或修改同步状态:
-
getState()
获取当前同步状态
-
setState(int newState)
设置当前同步状态
-
compareAndSetState(int expect, int update)
使用 CAS 设置当前状态,该方法能保证状态设置的原子性
2. 同步器可重写的方法
方法名称 | 描述 |
---|---|
protected boolean tryAcquire(int arg) | 独占式获取同步状态,实现该方法需要查询当前状态,并判断同步状态是否符合预期,然后再进行 CAS 设置同步状态 |
protected boolean tryRelease(int arg) | 独占式地释放同步状态,等待获取同步状态的线程将有机会获取同步状态 |
protected int tryAcquireShared(int arg) | 共享式获取同步状态,返回大于等于 0 的值,表示获取成功,否则获取失败 |
protected boolean tryReleaseShared(int arg) | 共享式释放同步状态 |
protected boolean isHeldExclusively() | 当前同步器是否在独占模式下被线程占有,一般该方法表示是否被当前线程所独占 |
3. 同步器提供的模板方法
方法名称 | 描述 |
---|---|
void acquire(int arg) | 独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的 tryAcquire(int arg) 方法 |
void acquireInterruptibly(int arg) | 与 acquire(int arg) 相同,但该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出 InterruptedException 并返回 |
boolean tryAcquireNanos(int arg, long nanos) | 在 acquireInterruptibly(int arg) 的基础上增加了超时限制 |
void acquireShared(int arg) | 共享式的获取同步状态,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态 |
void acquireSharedInterruptibly(int arg) | 与 acquireShared(int arg) 相同,该方法响应中断 |
boolean tryAcquireSharedNanos(int arg, long nanos) | 在 acquireSharedInterruptibly 的基础上增加了超时限制 |
boolean release(int arg) | 独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒 |
boolean releaseShared(int arg) | 共享式的释放同步状态 |
Collection<Thread> getQueuedThreads() | 获取等待在同步队列上的线程集合 |
4. 示例
下面通过一个独占锁的示例来深入了解一下同步器的工作原理。顾名思义,独占锁就是在同一时刻只能有一个线程获取到锁,其他获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能获取锁
public class Mutex implements Lock { /** * 自定义同步器 */ private static class Sync extends AbstractQueuedSynchronizer { @Override protected boolean isHeldExclusively() { // 是否处于占用状态 return getState() == 1; } @Override public boolean tryAcquire(int acquires) { // 当状态为 0 时获取锁 if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } @Override protected boolean tryRelease(int releases) { // 释放锁,将状态设置为 0 if (getState() == 0) { throw new IllegalMonitorStateException(); } setExclusiveOwnerThread(null); setState(0); return true; } /** * 返回一个 Condition, 每个 condition 都包含一个 condition 队列 */ Condition newCondition() { return new ConditionObject(); } } private final Sync sync = new Sync(); @Override public void lock() { sync.acquire(1); } @Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } @Override public boolean tryLock() { return sync.tryAcquire(1); } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(time)); } @Override public void unlock() { sync.release(1); } @Override public Condition newCondition() { return sync.newCondition(); }}
Mutex 中定义了一个静态内部类,该内部类继承了同步器并实现了独占式获取和释放同步状态。用户使用 Mutex 时并不会直接和内部同步器实现打交道,而是调用 Mutex 提供的方法,大大降低了实现一个可靠自定义组件的门槛
队列同步器的实现
1. 同步队列
同步器依赖内部的同步双向队列来完成同步状态的管理,当前线程获取同步状态失败后,同步器会将当前线程及其等待状态等信息构造成一个节点,并加入同步队列,同时阻塞当前线程。当同步状态释放后,会把首节点中的线程唤醒,使其再次尝试获取同步状态
节点是构成同步队列的基础,同步器拥有首节点(head)和尾结点(tail),没有成功获取同步状态的线程将会成为节点并加入该队列的尾部
同步队列的基本结构如下:
同步器将节点加入到同步队列的过程如图所示:
首节点是获取同步状态成功的节点,首节点线程在释放同步状态时,会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点,过程如下:
<.........上一篇: EventQueue
下一篇: 上机操作 - 堆栈、队列(Java 实现