AQS 为什么采用抽象类(abstract class)而不是接口(interface)实现?
AQS 为什么采用抽象类实现而不是接口?
AQS
(AbstractQueuedSynchronizer)是 Java 并发包 java.util.concurrent.locks
的核心组件,被广泛用于实现 ReentrantLock
、Semaphore
、CountDownLatch
等同步器。
核心答案
因为 AQS 需要共享状态管理、提供默认实现、定义模板方法,并且要被子类继承扩展,而接口无法满足这些需求。抽象类是实现“模板方法模式 + 状态封装”的最佳选择。
一、接口 vs 抽象类:本质区别回顾
特性 | 接口(Interface) | 抽象类(Abstract Class) |
---|---|---|
是否支持字段(成员变量) | ❌ 不支持(Java 8+ 支持 static final) | ✅ 支持实例字段 |
是否支持方法实现 | ✅ 默认方法(default) | ✅ 支持具体方法 |
是否支持构造器 | ❌ | ✅ |
是否支持继承状态 | ❌ | ✅ |
是否支持模板方法模式 | 有限支持 | ✅ 完美支持 |
AQS 需要一个能封装状态、提供公共逻辑、允许子类定制行为的结构 —— 这正是抽象类的强项。
二、AQS 的核心职责
AQS 的主要职责是:
- 管理同步状态(state):通过
volatile int state
表示资源的占用情况(如锁是否被持有)。 - 管理线程排队:使用 FIFO 等待队列(CLH 队列变种)管理等待线程。
- 提供阻塞/唤醒机制:基于
LockSupport.park()
/unpark()
。 - 定义同步语义:由子类决定“如何获取/释放资源”(如独占、共享、公平/非公平)。
三、为什么不能用接口实现?
如果 AQS 是一个接口,会面临以下致命问题:
1. 无法定义共享状态(state)
// AQS 内部有:
private volatile int state;
- 接口不能有实例字段(只能有 public static final 常量)。
- 而 state 是每个同步器实例独立的状态(如一个 ReentrantLock 有一个自己的 state),必须是实例变量。
- 接口无法封装状态 → 无法实现 AQS 的核心功能。
❌ 2. 无法提供默认实现
AQS 提供了大量公共逻辑,例如:
线程入队、出队
线程阻塞与唤醒
条件队列管理
自旋与阻塞策略
这些逻辑是通用的,不应由每个实现类重复编写。
接口(Java 8 前)无法提供方法实现。
即使使用 default 方法,也无法访问实例字段(如 state),因为接口没有状态。
❌ 3. 无法使用模板方法模式
AQS 使用了经典的 模板方法模式(Template Method Pattern):
// AQS 中的模板方法
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
- acquire() 是final 方法,封装了完整的获取流程。
- tryAcquire(arg) 是抽象方法,由子类实现具体逻辑(如是否可重入、是否公平)。
这种“父类定义流程,子类实现细节”的模式,只能通过抽象类实现。
接口虽然可以有 default 方法,但无法定义 protected 方法或字段,也无法控制子类的继承结构。
四、抽象类的优势完美契合 AQS
AQS需求 | 实现方式 |
---|---|
封装共享状态 | 定义 volatile int state 和 private transient Node head, tail 等字段 |
提供公共算法骨架 | 提供 acquire 、release 、acquireShared 等 final 模板方法 |
允许子类定制行为 | 定义 tryAcquire 、tryRelease 等 protected abstract 方法 |
隐藏复杂实现细节 | 队列管理、线程阻塞等细节对子类透明 |
支持继承与扩展 | 子类(如 ReentrantLock.Sync )继承 AQS,复用所有逻辑 |
五、举个例子:ReentrantLock 如何使用 AQS
class ReentrantLock {abstract static class Sync extends AbstractQueuedSynchronizer {// 子类实现:尝试获取锁protected final boolean tryAcquire(int acquires) {// 实现可重入、公平/非公平逻辑}}
}
- Sync 继承 AQS,复用其队列管理、阻塞唤醒逻辑。
- 只需实现 tryAcquire 等少数方法,即可实现完整的锁语义。
- 如果 AQS 是接口,Sync 就必须自己实现所有排队、阻塞逻辑,代码量巨大且易错。
六、为什么不用普通类(非抽象)?
因为 AQS 本身不决定“如何获取资源”,这部分必须由子类决定。
所以 tryAcquire 等方法必须是 abstract 的,强制子类实现。
因此必须是 abstract class,不能是普通类。
七、总结
原因 | 说明 |
---|---|
需要封装状态 | AQS 需要 state、head、tail 等实例字段,接口无法支持 |
需要提供默认实现 | 队列管理、阻塞唤醒等通用逻辑必须由 AQS 实现 |
使用模板方法模式 | acquire 是模板方法,tryAcquire 是钩子方法,抽象类是标准实现方式 |
支持继承与扩展 | 子类只需实现少数抽象方法,即可获得完整同步功能 |
接口无法满足需求 | 接口无状态、无构造器、难以实现复杂继承体系 |
一句话总结:
因为 AQS 需要封装共享状态(如 state)、提供通用的线程排队与阻塞逻辑,并通过模板方法模式让子类定制同步语义,而接口无法定义实例字段和提供默认实现,只有抽象类才能同时满足状态封装、代码复用和扩展性的要求。