2026年7月11日 · 5 分钟阅读
JUC 核心原理:CAS、Atomic、Lock、AQS 与 ReentrantLock
梳理 JUC 中 Atomic 原子类、CAS 原理与缺陷、Java 锁分类、synchronized 和 JUC 锁对比、AQS 队列以及 ReentrantLock 获取和释放锁流程。
JUC 是 java.util.concurrent 包的简称,是 Java 并发编程的核心工具箱。
如果说 synchronized 和 volatile 是并发基础关键字,那么 JUC 提供的是更丰富的工程化能力:原子类、显式锁、同步器、并发容器、阻塞队列、线程池。
Atomic 原子类
Atomic 包提供了一组基于 CAS 的原子操作类。
常见类型包括:
| 类型 | 示例 |
|---|---|
| 基本类型原子类 | AtomicInteger、AtomicLong、AtomicBoolean |
| 引用类型原子类 | AtomicReference |
| 数组原子类 | AtomicIntegerArray |
| 字段更新器 | AtomicIntegerFieldUpdater |
| 高并发累加器 | LongAdder、LongAccumulator |
例如计数器可以这样写:
AtomicInteger count = new AtomicInteger();
count.incrementAndGet();
它比 volatile int count; count++; 更安全,因为递增操作是原子的。
CAS 是什么
CAS 全称 Compare And Swap,比较并交换。
它包含三个值:
- 内存位置 V。
- 预期旧值 A。
- 准备写入的新值 B。
只有当 V 当前值等于 A 时,才把 V 更新为 B;否则说明其他线程改过了,本次更新失败。
CAS 是乐观锁思想:先不加锁,直接尝试更新;失败了再重试。
CAS 的优势和问题
CAS 的优势是避免阻塞线程,竞争不激烈时性能很好。
但它也有缺陷:
| 问题 | 说明 |
|---|---|
| ABA 问题 | 值从 A 变成 B 又变回 A,CAS 看不出中间变化 |
| 自旋开销 | 竞争激烈时反复重试会消耗 CPU |
| 单变量限制 | 普通 CAS 只能保证一个变量的原子更新 |
ABA 问题可以用版本号解决,例如 AtomicStampedReference。
Java 锁分类
Java 锁可以从多个维度分类。
| 分类维度 | 类型 |
|---|---|
| 是否阻塞 | 悲观锁、乐观锁 |
| 是否可重入 | 可重入锁、不可重入锁 |
| 是否公平 | 公平锁、非公平锁 |
| 是否共享 | 独占锁、共享锁 |
| 是否可中断 | 可中断锁、不可中断锁 |
synchronized 和 ReentrantLock 都是可重入的独占锁。
synchronized 和 ReentrantLock 对比
| 能力 | synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | JVM 关键字 | JUC 类 |
| 自动释放 | 支持,异常时自动释放 | 需要 finally 手动释放 |
| 可中断获取锁 | 不支持 | 支持 |
| 超时获取锁 | 不支持 | 支持 |
| 公平锁 | 不支持直接配置 | 支持公平/非公平 |
| 多条件队列 | 依赖 wait/notify | 支持多个 Condition |
如果只是简单临界区,synchronized 更简洁;如果需要超时、中断、公平锁、多个等待条件,ReentrantLock 更灵活。
AQS 是什么
AQS 全称 AbstractQueuedSynchronizer,队列同步器。它是很多 JUC 同步组件的基础。
基于 AQS 实现的组件包括:
ReentrantLockReentrantReadWriteLockSemaphoreCountDownLatchFutureTask
AQS 的核心思想是:用一个 state 表示同步状态,用一个 FIFO 双向队列管理获取同步状态失败的线程。
AQS 同步队列
当线程获取锁失败时,会被包装成 Node 节点,加入 AQS 同步队列。
可以简化理解为:
head <-> Node(threadA) <-> Node(threadB) <-> tail
队列中的线程会按一定规则挂起和唤醒。锁释放后,AQS 会唤醒后继节点,让它重新竞争同步状态。
ReentrantLock 获取锁流程
ReentrantLock.lock() 最终会委托内部 Sync 对象获取锁。
简化流程:
- 尝试用 CAS 把
state从 0 改为 1。 - 成功则当前线程成为锁持有者。
- 如果锁已经被当前线程持有,则
state + 1,实现重入。 - 如果获取失败,线程进入 AQS 队列。
- 队列中的线程自旋判断自己是否有资格获取锁。
- 不满足条件时通过
LockSupport.park()挂起。
可重入的本质就是同一个线程再次获取锁时不阻塞,而是增加重入计数。
ReentrantLock 释放锁流程
释放锁时调用 unlock(),最终会减少 state。
简化流程:
- 判断当前线程是否为锁持有者。
- 如果不是,抛出异常。
- 如果是,将
state减 1。 - 如果
state还不为 0,说明只是释放了一层重入。 - 如果
state变为 0,锁完全释放。 - 唤醒 AQS 队列中的后继线程。
使用 ReentrantLock 必须在 finally 中释放锁:
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
公平锁和非公平锁
公平锁会按照队列顺序获取锁,先来先得。非公平锁允许线程插队,直接尝试抢锁。
| 类型 | 优点 | 缺点 |
|---|---|---|
| 公平锁 | 等待时间更可控 | 吞吐量可能较低 |
| 非公平锁 | 吞吐量通常更高 | 可能出现线程等待更久 |
ReentrantLock 默认是非公平锁,因为它在多数场景下性能更好。
读写锁
ReentrantReadWriteLock 把锁分成读锁和写锁。
- 多个读线程可以同时持有读锁。
- 写线程持有写锁时,其他读写线程都要等待。
适合读多写少的场景,比如配置缓存、路由表、规则表等。
小结
JUC 的核心可以这样串起来:
- Atomic 原子类依赖 CAS 实现无锁更新。
- CAS 适合低竞争场景,但有 ABA 和自旋开销问题。
- Lock 提供比
synchronized更灵活的锁能力。 - AQS 用
state和 FIFO 队列抽象同步器。 ReentrantLock是理解 AQS 的最佳入口。
掌握 AQS 后,再看 CountDownLatch、Semaphore、FutureTask 等工具,就会发现它们只是同步状态和队列规则不同。