主题
界面
主题
《synchronized 与 volatile》 的手段只有两种;本节打开无锁并发(CAS)与锁的增强版(Lock/AQS):
★ 本节实测(LockCasLesson):
CAS 10 万次自增:一次不丢(纯无锁)✓✓
ABA:普通 CAS 误判,版本号 CAS 察觉 ✓✓
可中断锁:死锁解除 ✓✓✓★ synchronized 三个做不到 ✗:
等锁不可中断(死锁只能重启)✗✗;不能超时 ✗;没有公平模式 ✗
CAS 则提供另一条路:不上锁,用「失败重试」实现原子性 ✓✓
读旧值 → 算新值 → 旧值没变才写回(CPU 原子指令)✓
失败就自旋重试,不上锁 —— 所以叫「无锁并发」✓✓实测:AtomicInteger 10 万次自增(100 线程)一次不丢 ✓✓
★ 边界 ✓✓:低竞争 CAS 优;高竞争自旋重试耗 CPU ⚠️ → LongAdder 分段 ✓
值 A → B → A:CAS 察觉不到(值没变)✗实测:普通引用 CAS 误判成功 ✗✗;AtomicStampedReference(版本号)察觉 ✓✓
★ 何时危险 ✗✗:值 = 余额(被扣又退回 → 重复扣款)✗✗ 解法:版本号 / 时间戳 / 数据库条件 UPDATE(项目 A/B 本质就是它 ✓✓)
| 能力 | synchronized | ReentrantLock |
|---|---|---|
| 可超时 tryLock(ms) | 没有 ✗ | 有 ✓✓ |
| 可中断 | 没有 ✗✗ | lockInterruptibly ✓✓✓ |
| 公平模式 | 没有 | 有 ⚠️(吞吐降) |
| 条件队列 | 单个 | 多个 Condition ✓ |
| 使用难度 | 简单(自动释放) ✓✓ | 必须 finally unlock ✗✗ |
实测:公平锁按申请顺序拿锁 ✓✓
★★ 默认选 synchronized,需要上述能力才换 Lock ✓✓✓
实测:两线程互等(死锁)→ 中断其中一个 → 死锁解除 ✓✓✓
synchronized 等锁不可中断 ✗✗ —— 死锁只能重启进程 ✗✗ (这也是 jstack 发现死锁后没有别的办法的原因 ⚠️)
| 本实验用的 | AQS 对应 |
|---|---|
| ReentrantLock.lock() | 独占模式(state 0→1)✓ |
| lockInterruptibly() | 等锁线程在队列中被中断唤醒 ✓✓ |
| Semaphore / CountDownLatch | 共享模式(state 递减)✓✓ |
| Condition | AQS 条件队列 ✓ |
★ 一句话:你只实现「state 怎么算拿到/释放」,排队与唤醒它帮你做 ✓✓✓
counter.incrementAndGet(); // CAS 自旋,无锁 ✓if (lock.tryLock(1, TimeUnit.SECONDS)) { // 1 秒拿不到就放弃
try { ... } finally { lock.unlock(); }
}lock.lock();
doSomething(); // ✗ 抛异常 → 永不释放 → 全部线程卡死 ✗✗
lock.unlock(); // 必须放 finally ✓✓代码:labs/java-deep/src/main/java/cn/kb/learn/deep/concurrency/LockCasLesson.java
实测输出(关键部分):
===== 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 时被中断,放弃 ✓✓(死锁解除)| # | 错误 | 说明 |
|---|---|---|
| 1 | ReentrantLock 忘 finally unlock ✗✗✗ | 死等 ✓✓ |
| 2 | 忽视 ABA ✗✗ | 余额类场景重复扣款 ✓✓ |
| 3 | 高竞争硬用 CAS 自旋 ✗ | CPU 空转 → LongAdder ✓ |
| 4 | 公平锁当默认 ✗ | 吞吐下降 ⚠️ |
| 5 | 中断后不恢复中断标志 ✗ | 吞掉中断信号 ⚠️ |
验收方式:能画出 AQS 的「state + 队列」模型。
扩展任务:用 AQS 自定义一个只允许 2 个线程同时执行的同步器(Semaphore(2) 的简化版)✓✓