AQS 中独占模式(Exclusive)和共享模式(Share)的区别?
在 Java 的并发编程中,AQS(AbstractQueuedSynchronizer) 是构建锁和同步器的核心框架。AQS 内部维护了一个 volatile int state(同步状态)和一个 FIFO 的双向链表(等待队列)。
AQS 支持两种同步模式:独占模式(Exclusive) 和 共享模式(Share)。它们的根本区别在于能否有多个线程同时获取到同步资源。
以下是两者的详细对比和原理解析:
1. 核心概念对比
独占模式(Exclusive):
- 定义:同一时刻,只能有一个线程获取到同步资源。其他尝试获取资源的线程都会被阻塞并加入到等待队列中。
- 比喻:单人洗手间。一个人进去后门就锁上了,其他人必须在外面排队,等里面的人出来才能进去下一个。
- 典型应用:
ReentrantLock。
共享模式(Shared):
- 定义:同一时刻,可以有多个线程同时获取到同步资源(只要资源数量允许)。
- 比喻:有 N 个座位的阅览室。只要还有空座位,多个人可以同时进去看书;如果没有空座位了,新来的人才需要排队。
- 典型应用:
Semaphore(信号量)、CountDownLatch(倒计时器)、ReentrantReadWriteLock的读锁。
2. 唤醒机制的区别(最关键的底层差异)
在 AQS 的等待队列中,当持有锁的线程释放资源后,如何唤醒后续排队的线程,是这两种模式最大的不同:
独占模式的唤醒(点对点):
- 当独占锁释放时,它只会唤醒等待队列中紧接着的下一个节点(即头节点的后继节点)。
- 流程:线程 A 释放锁 -> 唤醒线程 B -> 线程 B 获取锁 -> 结束。
共享模式的唤醒(传播机制 Propagate):
- 当一个线程以共享模式成功获取资源后,如果资源还有剩余(
state > 0),它不仅自己会获取资源,还会主动唤醒下一个排队的共享节点。下一个节点获取成功后,如果还有剩余资源,会继续唤醒下下个节点,形成连锁反应(传播)。 - 当共享锁释放时,也会触发唤醒后续节点的操作。
- 流程:线程 A 获取共享锁 -> 发现还有资源 -> 唤醒排在后面的线程 B -> 线程 B 获取锁 -> 发现还有资源 -> 唤醒线程 C... 直到资源耗尽。
- 当一个线程以共享模式成功获取资源后,如果资源还有剩余(
3. 需要重写的方法不同
AQS 是基于模板方法模式设计的,自定义同步器时,需要根据使用哪种模式来重写不同的方法:
| 模式 | 需要重写的方法 | 返回值含义 |
|---|---|---|
| 独占模式 | tryAcquire(int arg) |
boolean:true 表示获取成功,false 表示失败排队。 |
tryRelease(int arg) |
boolean:true 表示释放成功并可以唤醒后续节点,false 表示释放失败。 |
|
| 共享模式 | tryAcquireShared(int arg) |
int:< 0:获取失败,需排队。 = 0:获取成功,但没有剩余资源,无需唤醒后续节点。 > 0:获取成功,且有剩余资源,需要唤醒后续排队节点。 |
tryReleaseShared(int arg) |
boolean:true 表示释放成功且可以唤醒后续节点。 |
4. 节点(Node)的标记不同
AQS 的等待队列(CLH 队列)中的节点(Node),在创建时会标记其等待的模式:
Node.EXCLUSIVE:独占模式节点。表示该线程正在等待独占锁。Node.SHARED:共享模式节点。表示该线程正在等待共享锁。
注:一个 AQS 的队列中,是可以同时存在独占节点和共享节点的(例如 ReentrantReadWriteLock 的等待队列)。
5. 能否混合使用?
可以混合使用。 AQS 允许在一个同步器中同时实现独占模式和共享模式。
典型代表:ReentrantReadWriteLock(读写锁)
- 写锁(WriteLock) 使用了独占模式:写和写互斥、读和写互斥。调用的是
acquire()和release()。 - 读锁(ReadLock) 使用了共享模式:读和读不互斥,多个线程可以同时读取。调用的是
acquireShared()和releaseShared()。 - 它巧妙地将 AQS 的一个 32 位的
state变量拆分为两部分:高 16 位表示读锁(共享)的持有次数,低 16 位表示写锁(独占)的持有次数。
总结表
| 特性 | 独占模式 (Exclusive) | 共享模式 (Shared) |
|---|---|---|
| 资源访问 | 仅限单一线程 | 允许多个线程同时访问 |
| 状态(state)含义 | 通常 0 为可用,1 为被占用(重入时递增) | 通常表示可用资源的数量或屏障的状态 |
| 获取资源方法 | acquire() / tryAcquire() |
acquireShared() / tryAcquireShared() |
| 释放资源方法 | release() / tryRelease() |
releaseShared() / tryReleaseShared() |
| 唤醒机制 | 仅唤醒队列中的下一个有效节点 | 传播机制(Propagate),不仅自己获取,还会唤醒后续共享节点 |
| 典型实现类 | ReentrantLock |
Semaphore, CountDownLatch |