Skip to content

Lock、CAS 与 AQS ​

S6-003建议学高级约 90 分钟JDK 21Java 高级与架构 · 并发编程

1. 本节定位 ​

《synchronized 与 volatile》 的手段只有两种;本节打开无锁并发(CAS)与锁的增强版(Lock/AQS):

★ 本节实测(LockCasLesson):

text
CAS 10 万次自增:一次不丢(纯无锁)✓✓
ABA:普通 CAS 误判,版本号 CAS 察觉 ✓✓
可中断锁:死锁解除 ✓✓✓

2. 学习目标 ​

  • 理解 CAS 原理与适用边界(低竞争优/高竞争自旋耗 CPU)★★★;
  • 知道 ABA 问题与版本号解法 ★★★;
  • 知道 ReentrantLock 相比 synchronized 的三个增强与一个代价 ★★★;
  • 理解 AQS 是「state + 等待队列」的骨架 ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

★ synchronized 三个做不到 ✗:

等锁不可中断(死锁只能重启)✗✗;不能超时 ✗;没有公平模式 ✗

CAS 则提供另一条路:不上锁,用「失败重试」实现原子性 ✓✓

5. 核心概念 ​

5.1 CAS:无锁的原子 ★★★ ​

text
读旧值 → 算新值 → 旧值没变才写回(CPU 原子指令)✓
失败就自旋重试,不上锁 —— 所以叫「无锁并发」✓✓

实测:AtomicInteger 10 万次自增(100 线程)一次不丢 ✓✓

★ 边界 ✓✓:低竞争 CAS 优;高竞争自旋重试耗 CPU ⚠️ → LongAdder 分段 ✓

5.2 ABA 与版本号 ★★★ ​

text
值 A → B → A:CAS 察觉不到(值没变)✗

实测:普通引用 CAS 误判成功 ✗✗;AtomicStampedReference(版本号)察觉 ✓✓

★ 何时危险 ✗✗:值 = 余额(被扣又退回 → 重复扣款)✗✗ 解法:版本号 / 时间戳 / 数据库条件 UPDATE(项目 A/B 本质就是它 ✓✓)

5.3 ReentrantLock 能力矩阵 ★★★ ​

能力synchronizedReentrantLock
可超时 tryLock(ms)没有 ✗有 ✓✓
可中断没有 ✗✗lockInterruptibly ✓✓✓
公平模式没有有 ⚠️(吞吐降)
条件队列单个多个 Condition ✓
使用难度简单(自动释放) ✓✓必须 finally unlock ✗✗

实测:公平锁按申请顺序拿锁 ✓✓

★★ 默认选 synchronized,需要上述能力才换 Lock ✓✓✓

5.4 可中断锁:死锁的逃生门 ★★★ ​

实测:两线程互等(死锁)→ 中断其中一个 → 死锁解除 ✓✓✓

synchronized 等锁不可中断 ✗✗ —— 死锁只能重启进程 ✗✗ (这也是 jstack 发现死锁后没有别的办法的原因 ⚠️)

5.5 AQS:state + 等待队列的骨架 ★★★ ​

本实验用的AQS 对应
ReentrantLock.lock()独占模式(state 0→1)✓
lockInterruptibly()等锁线程在队列中被中断唤醒 ✓✓
Semaphore / CountDownLatch共享模式(state 递减)✓✓
ConditionAQS 条件队列 ✓

★ 一句话:你只实现「state 怎么算拿到/释放」,排队与唤醒它帮你做 ✓✓✓

6. 递进示例 ​

6.1 CAS 自增 ​

java
counter.incrementAndGet();   // CAS 自旋,无锁 ✓

6.2 超时获取 ​

java
if (lock.tryLock(1, TimeUnit.SECONDS)) {     // 1 秒拿不到就放弃
    try { ... } finally { lock.unlock(); }
}

6.3 反例:ReentrantLock 忘记 unlock ​

java
lock.lock();
doSomething();        // ✗ 抛异常 → 永不释放 → 全部线程卡死 ✗✗
lock.unlock();        // 必须放 finally ✓✓

7. 完整可运行代码与实测 ​

代码:labs/java-deep/src/main/java/cn/kb/learn/deep/concurrency/LockCasLesson.java

实测输出(关键部分):

text
===== 1. CAS:AtomicInteger 10 万次自增 =====
  期望 100000,实际 100000 —— 一次不丢 ✓✓(纯无锁)
===== 2. ABA:值 A→B→A,普通 CAS 察觉不到 =====
  普通 CAS(1→3):成功?true ✗✗(中途被改成 2 再改回 1,CAS 以为没动过)
  带版本号 CAS(1→3):成功?false ✓✓(值回到 1,但盖章从 0 变到 2,写入被拒)
===== 3. 公平锁 =====
  线程-A → 线程-B → 线程-C(按申请顺序)✓✓
===== 4. 可中断锁 =====
  线程-1 等 B 时被中断,放弃 ✓✓(死锁解除)

8. Debug 观察点 ​

  1. 高竞争(100 线程)对比 AtomicLong vs LongAdder ✓✓;
  2. 用 jstack 看死锁(找 "Found one Java-level deadlock")✓✓;
  3. 公平锁吞吐 vs 非公平锁 ⚠️。

9. 常见错误 ​

#错误说明
1ReentrantLock 忘 finally unlock ✗✗✗死等 ✓✓
2忽视 ABA ✗✗余额类场景重复扣款 ✓✓
3高竞争硬用 CAS 自旋 ✗CPU 空转 → LongAdder ✓
4公平锁当默认 ✗吞吐下降 ⚠️
5中断后不恢复中断标志 ✗吞掉中断信号 ⚠️

10. 动手实验 ​

  1. 跑三个实验,记录数据 ✓✓;
  2. 把 AtomicInteger 换 LongAdder,高竞争下对比 ✓✓;
  3. 构造死锁并用 jstack 确认,再用 lockInterruptibly 解除 ★★★。

验收方式:能画出 AQS 的「state + 队列」模型。

扩展任务:用 AQS 自定义一个只允许 2 个线程同时执行的同步器(Semaphore(2) 的简化版)✓✓

11. 练习题 ​

  1. CAS 三个词的全称?
  2. ABA 的本质是什么(CAS 只比较了哪里的值)?解法是什么?
  3. synchronized 与 ReentrantLock 怎么选?
  4. Semaphore 与 CountDownLatch 在 AQS 里是什么模式?
  5. 为什么 LongAdder 高竞争更快?
参考答案(要点)
  1. Compare-And-Swap(比较并交换)✓
  2. 内存;版本号/时间戳(AtomicStampedReference)✓★
  3. 默认 synchronized(简单、自动释放)✓✓;需要可中断/超时/公平/多条件才换 Lock ✓✓★
  4. 都是共享模式 ✓(state 递减)★
  5. 分段求和(Cell 数组摊开竞争)✓✓★

12. 掌握标准 ​

  • [ ] 理解 CAS 原理与边界 ★★★;
  • [ ] 知道 ABA 与解法 ★★★;
  • [ ] 知道 ReentrantLock 的增强与代价 ★★★;
  • [ ] 理解 AQS 骨架 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

《线程池与 ThreadPoolExecutor》。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引