适用读者:想知道
ReentrantLock凭什么比synchronized灵活的人。
前置:S05-B03。
实测环境:OpenJDK 21.0.12.1(2026-11-05)。
覆盖旧文:A-1122(312) A-1123(227) A-1121(217) A-1595(340) + 1582(111) 945(75) 1601(77) = 1,359 点。
覆盖扩展:E-S05-008(state 与 CLH 队列)。
上一篇:S05-B05 · 线程池:7 个参数、拒绝策略与动态调整
下一篇:S05-J02 · 并发容器。
1. 一句话结论
AQS = 一个 volatile int state + 一个双向等待队列(CLH 变体)。所有同步器(ReentrantLock/Semaphore/CountDownLatch)都只是"怎么解释 state"。
2. 结构(E-S05-008)
| 组件 | 作用 |
|---|---|
volatile int state |
同步状态(ReentrantLock 里 = 重入次数) |
| CLH 双向队列 | 等锁线程排队(Node 含 prev/next/waitStatus) |
tryAcquire/tryRelease |
由子类实现(独占) |
tryAcquireShared/tryReleaseShared |
共享模式(Semaphore/CountDownLatch) |
"独占 vs 共享"是 AQS 的唯一分野:
| 模式 | 例子 | state 含义 |
|---|---|---|
| 独占 | ReentrantLock |
重入次数 |
| 共享 | Semaphore |
剩余许可数 |
3. 实测:可重入与 tryLock
ReentrantLock lock = new ReentrantLock(true); // 公平
lock.lock(); lock.lock();
System.out.println(lock.getHoldCount()); // → 2
实测输出:
1. 可重入 holdCount = 2
2. tryLock(200ms) 对端持有 800ms → false
| 事实 | 说明 |
|---|---|
holdCount = 2 |
state 累加,同一线程可重入 |
tryLock(200ms) → false |
超时拿不到就返回 false(不会一直等) |
这两个能力 synchronized 都没有:它是"要么阻塞到拿到,要么拿不到就阻塞",没有"等 200ms 不给我就走"。
4. 公平锁 vs 非公平锁(A-1121)
| 类型 | 行为 | 代价 |
|---|---|---|
| 非公平(默认) | 新来的线程可直接抢(插队) | 可能饿死排队线程,但吞吐更高 |
| 公平 | 严格按队列顺序 | 吞吐略低(每次要检查队列) |
实测佐证:new ReentrantLock(true) 是公平锁;默认 new ReentrantLock() 是非公平。
判据:"能否容忍个别线程长时间拿不到锁?" 不能 → 公平;否则用非公平(默认)。
5. tryLock 的三种用法
if (lock.tryLock()) { ... } // 立即返回
if (lock.tryLock(200, TimeUnit.MILLISECONDS)) { ... } // 超时
if (lock.tryLock(200, MS)) { } else { 降级逻辑 } // 拿不到就降级
典型场景:避免死锁——用超时拿不到的线程主动放弃并重试,破坏"循环等待"。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "AQS 是锁" | 是同步器框架,锁只是其中一种 |
| "公平锁一定更好" | 吞吐更低,默认非公平 |
"tryLock 不阻塞" |
无参不阻塞;带超时会最多等那么久 |
"unlock 可以省略" |
必须 finally,否则永久占用 |
"holdCount 不会为 0" |
每次 unlock 减 1,归零才真正释放 |
7. 自检
- AQS 的两个核心组成是什么?
- 独占与共享模式的差别?各举例。
- 公平锁与非公平锁的取舍?
tryLock怎么用来避免死锁?- 为什么
unlock必须写在finally?
8. 下一篇
S05-J02 · 并发容器:CHM、阻塞队列、COW
AQS 讲完,下一篇讲"不用锁也能安全"的容器:ConcurrentHashMap 为什么在 JDK 8 废弃分段锁、COW 适合什么场景。
本篇属于系列《S05 · Java 并发与 JVM》。旧文来源:A-1122/1123/1121/1595/1582/945/1601;扩展点 E-S05-008。实测为 OpenJDK 21。