JUC 锁与 AQS:JUC 同步器的统一骨架
更新时间:2026-09-20。
java.util.concurrent里的锁、信号量、闩锁看起来各不相同,底层却共享同一个框架 AQS(AbstractQueuedSynchronizer)。看懂 CAS + state + 等待队列三件套,就看懂了整个 JUC。前置:synchronized 与 volatile/JMM。
一、CAS:无锁并发的原子指令
CAS(Compare And Swap) 比较「内存当前值」与「期望值」是否相等,相等才更新为新值,整个动作由 CPU 保证原子:
bash
CAS(内存位置 V, 期望旧值 A, 新值 B):
if V == A → V = B,成功
else → 失败(说明被别人改过),调用方自旋重试AtomicInteger.incrementAndGet()就是循环 CAS(读旧值→算新值→CAS,失败再来),不用加锁。- ABA 问题:值从 A→B→A,CAS 只看最终相等无法察觉中途被改过;用版本号
AtomicStampedReference解决。 - 竞争极高时纯自旋会烧 CPU,所以 AQS 在抢不到时会把线程挂起,而不是无限忙等。
二、AQS:state + CLH 等待队列
AQS 用一个 volatile int state 表示同步状态,子类定义 state 的语义;拿不到资源的线程被封装成节点进入一条 FIFO 的 CLH 变体队列阻塞,释放时唤醒队首:
| 同步器 | state 的含义 |
|---|---|
| ReentrantLock | 0 空闲;≥1 被持有(可重入时累加,记录独占线程) |
| Semaphore | 剩余许可数 |
| CountDownLatch | 剩余计数,归零放行所有等待者 |
| ReentrantReadWriteLock | 高 16 位读锁数、低 16 位写锁数 |
bash
线程1 CAS 抢 state 成功 → 持锁
线程2 CAS 失败 → 入 CLH 队列 → LockSupport.park() 挂起
线程1 释放 state=0 → unpark 队首线程2 → 重新 CAS 抢锁AQS 还区分独占模式(一次一个线程,如 ReentrantLock)与共享模式(多个线程可同时通过,如 Semaphore/CountDownLatch)。
三、ReentrantLock 相比 synchronized 强在哪
java
ReentrantLock lock = new ReentrantLock(true); // true=公平锁
lock.lock();
try {
// 临界区
} finally {
lock.unlock(); // 必须在 finally 释放,否则异常后永久死锁
}
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) { // 可超时、可中断
try { /* ... */ } finally { lock.unlock(); }
} else {
// 等不到就降级,避免死等
}
Condition cond = lock.newCondition(); // 比 wait/notify 更精细的多条件队列| 能力 | synchronized | ReentrantLock |
|---|---|---|
| 可重入 | 是 | 是 |
| 公平锁 | 否(仅非公平) | 可选公平/非公平 |
| 可中断等待 | 否 | lockInterruptibly |
| 超时尝试 | 否 | tryLock(timeout) |
| 多条件队列 | 一个 wait 集 | 多个 Condition |
| 释放 | 自动 | 必须手动 finally unlock |
四、读写锁与常用工具
- ReentrantReadWriteLock:读读共享、读写/写写互斥,适合读多写少;写锁可降级为读锁,反之不可。
- StampedLock(Java 8):新增乐观读,读多写少吞吐更高,但不可重入、用错易出错。
- CountDownLatch:一次性倒计数,等 N 个条件满足(如多线程都初始化完)再放行。
- Semaphore:控制同时访问的许可数(限流/连接池)。
- 这些工具都基于 AQS,理解 state 语义即可举一反三。

坑
- 忘记在 finally 里 unlock:抛异常后锁不释放,后续全部卡死,这是与 synchronized 最大的使用风险。
- 默认非公平锁被误解为「不公平缺陷」:非公平允许插队,吞吐通常更高(减少挂起/唤醒),公平锁只在必须严格排队时用。
- 高并发只用 AtomicLong 自旋:竞争激烈时大量重试,用
LongAdder(热点分离)吞吐更好。 - 读写锁里持有读锁再想写:读锁不允许直接升级写锁,会自死锁,必须先释放读锁。
- 把 CAS 当无等待:CAS 是无锁但仍可能自旋空转,极端竞争下 AQS 挂起反而更省 CPU。
总结
JUC 同步器统一建立在 AQS 之上:AQS 用 volatile int state 表达资源、用 CAS 无锁抢占、抢不到就进 CLH 队列并 park、释放时 unpark 队首,子类只需定义 state 语义——ReentrantLock 的可重入次数、Semaphore 的许可、CountDownLatch 的计数、读写锁的高低位都由此而来;相比 synchronized,ReentrantLock 提供公平选择、可中断、tryLock 超时与多 Condition,但必须在 finally 手动 unlock。 原子性与可见性的语义边界见 volatile 与 JMM。