由 state 引发的 AbstractQueuedSynchronizer 面试连环炮(上)
把最复杂的变成最简单的,才是最高明的。
——达·芬奇
- 面试频率(看起来较低,但该类是正确理解其他同步器和Lock锁的基础)
Q:介绍下AbstractQueuedSynchronizer?
> 为行文紧凑,后文中 AQS 即代表AbstractQueuedSynchronizer类。
作用
- 该类提供了一种框架,用于实现依赖FIFO等待队列的阻塞锁和同步器(比如信号量)。
- 此类的设计旨在为大多数依赖单个原子int值表示 state 的同步器提供切实有用的基础。
同步状态字段 - state
代表加锁的状态,初始值为0。
- volatile 修饰保证线程间的可见性。

子类必须定义用于更改此 state 的 protected 方法,并定义该 state 对于 acquired 或 released 此对象而言意味着什么。鉴于这些,此类中的其他方法将执行全局排队和阻塞机制。子类可以维护其他状态字段,但是就同步而言,我们只跟踪通过如下方法: - getState - 同步状态的当前值。该操作具有 volatile 读的内存语义。
- setState - 设置同步状态的值。该操作具有 volatile 写的内存语义。
- compareAndSetState - 如果当前状态值等于期望值,则以原子方式将同步状态设置为给定的更新值。此操作具有 volatile 读和写的内存语义。

操作的原子更新的int值。
子类应定义为用于实现其所在类的同步属性的非 public 内部的辅助类。AQS不实现任何同步接口。 相反,它定义了诸如
- acquireInterruptible之类的方法

可以通过具体的锁和相关的同步器适当地调用这些方法来实现其 public 方法。
支持模式
此类支持独占模式(默认)和共享模式:
- 当以独占模式acquire时,其他线程尝试的acquired无法成功。独占模式有时只用 Sync Queue,但若涉及 Condition,则还有 Condition Queue。
- 由多线程acquire的共享模式可能(但不一定)成功。共享模式时只用 Sync Queue。
该类并不理解这些差异,只是从机制上说,当共享模式acquire成功时,下一个等待线程(如果存在)也必须确定它是否也可以获取。在不同模式下等待的线程共享相同的FIFO队列。 通常,实现的子类仅支持两种模式之一,但也可以同时出现,比如在ReadWriteLock中。仅支持单一模式的子类无需定义未支持模式的方法。
ConditionObject
此类定义了一个内置的 ConditionObject 类

可由支持独占模式的子类用作 Condition 的实现,
- 该子类的 isHeldExclusively 方法通知当前线程是否为独占同步。
以独占模式acquire,如果被中断则终止。通过首先检查中断状态,然后至少调用一次 tryAcquire(int) 来实现,并在成功后返回。否则,将线程排队,并可能反复阻塞和解除阻塞,并调用 tryAcquire(int) 直到成功或线程被中断。此方法可用于实现Lock.lockInterruptibly()方法。
使用当前 getState 的值调用方法 release 会完全释放此对象 ,根据给定的已保存的state值,acquire(int)最终将该对象恢复为其先前的acquire状态。否则,没有AQS方法创建这样的条件,因此,如果无法满足此约束,请不要使用它。ConditionObject的行为自然是取决于其同步器实现的语义。
此类提供了内部队列的inspection/instrumentation/monitoring方法以及条件对象的类似方法。 可以根据需要使用 AQS 将它们导出到类中以实现其同步机制。
此类的序列化仅存储基本的原子整数维护状态,因此反序列化的对象会具有空线程队列。 需要可序列化的典型子类将定义一个readObject方法,该方法可在反序列化时将其恢复为已知的初始状态。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
(ArrayBlockingQueue类提供了此功能,因此没有理由实现此示例用法类。)
Q:讲讲这个类一般怎么用?
要将此类用作同步器的基础,使用getState(), setState(int) 和/或 compareAndSetState(int, int)检查和/或修改同步状态,以重新定义以下方法:
tryAcquire(int)
尝试以独占模式acquire。此方法应查询对象的状态是否允许以独占模式获取对象,如果允许则获取对象。
始终由执行acquire的线程调用此方法。如果此方法通知失败,则acquire方***将线程排队(如果尚未排队),直到被其他线程通知释放为止。这可以用来实现方法Lock.tryLock()。
tryRelease(int)
尝试设置state以反映独占模式下的释放。
始终由执行release的线程调用此方法。
tryAcquireShared(int)
共享模式版本的tryAcquire(int)
tryReleaseShared(int)
共享模式版本的tryRelease(int)
isHeldExclusively()

可见,默认情况下它们都抛 UnsupportedOperationException。所以请根据需要在子类中重写。
这些方法的实现必须在内部是线程安全的,并且通常应简短且不阻塞。 定义这些方法是使用此类的唯一受支持的方法。 所有其他方法都被声明为final,因为它们不能独立变化,而且子类也无法重写!
从 AQS 继承的方法对跟踪具有独占同步器的线程很有用。 推荐使用它们-这将启用监视和诊断工具,以帮助用户确定哪些线程持有锁。
虽然此类基于内部的FIFO队列,但它不会自动执行FIFO获取策略。 独占同步的核心采用以下形式:
- Acquire
while (!tryAcquire(arg)) {
如果线程尚未入队,则将其加入队列;
可能阻塞当前线程;
}
- Release
if (tryRelease(arg))
取消阻塞第一个入队的线程;
共享模式与此相似。
因为 acquire 中的检查是入队前被调用的,所以新的获取线程可能先于被阻塞和排队的其他线程
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
<p> “挨踢”业行情日益严峻,企业招聘门槛愈来愈高,大厂hc更是少之又少,而Java技术面试普遍对基础知识的掌握考察特别深,大多数同学突击所看的 Java 面试基础知识点根本达不到面试官近乎挑剔的要求。 本专刊针对如今的校招及社招痛点,深入解析 JDK 的核心源码,探究 JDK 的设计精髓及最佳实践,同时以模拟面试的场景切入,让同学们在阅读过程中也能轻松掌握面试技巧。 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p>
