主题
界面
主题
《并发基础:线程、锁与内存模型》 提出问题;本节是两种同步手段的分工:
synchronized:互斥 + 可见性(重,但全能)
volatile :只有可见性(轻,但只读场景)★ 本节实测(ConcurrencyLesson):
synchronized:10 万次自增一次不丢(3.4 万微秒)✓✓
volatile int :丢 197 次更新 ✗✗ —— 「volatile 计数器」是错的 ✓✓✓★ 实测已证明 ✗✗:volatile 计数器丢 197 次;synchronized 同规模一次不丢。 但 volatile 不是没用 ——它是「停止标志、配置开关、双检锁」的必需品 ✓✓
| 能力 | synchronized | volatile |
|---|---|---|
| 原子性(读改写) | ✓ | ✗(实测丢 197) |
| 可见性 | ✓ | ✓ |
| 禁止重排 | ✓ | ✓ |
| 阻塞 | 会阻塞 | 不阻塞 ✓ |
| 范围 | 代码块/方法 | 单个变量 ✗ |
★★★ 选择口诀:只写一次多方读 → volatile ✓;读改写 → 锁或原子类 ✓;多变量一起变 → 锁 ✓
★ 反例回顾 ✗✗:volatile 计数器(本节实测复现)
无锁 → 偏向锁 → 轻量级锁(CAS 自旋)→ 重量级锁(OS 互斥量)JDK 15 起偏向锁已废弃 ✓;低竞争轻量级锁快 ✓;高竞争膨胀 ⚠️; 锁要粒度小、持有时间短 ✓✓(呼应 《Sentinel 限流与熔断》)
| 场景 | 手段 |
|---|---|
| 停止标志/开关 ★★★ | volatile boolean ✓ |
| 计数器/累加 ★★★ | AtomicLong / LongAdder ✓ |
| 复合不变量(多字段一起变)★★★ | synchronized ✓ |
| 单例 instance ★★ | volatile 双检锁,或静态内部类/枚举 ✓ |
| 读多写少的映射 ★★ | ConcurrentHashMap(《JUC 工具类与并发容器》)✓ |
private static volatile boolean running = true;
public void shutdown() { running = false; }private static volatile Instance instance; // ★ 必须 volatile
public static Instance get() {
if (instance == null) {
synchronized (Instance.class) {
if (instance == null) { instance = new Instance(); }
}
}
return instance;
}private volatile int count;
void increment() { count++; } // ✗✗ 可见性有了,原子性没有:实测丢 197 次更新
// 修复:synchronized increment() / AtomicInteger / LongAdder(高竞争最优)代码:labs/java-deep/src/main/java/cn/kb/learn/deep/concurrency/ConcurrencyLesson.java
实测输出:
===== 2. synchronized 修复 =====
期望 100000,实际 100000 —— 一次都没丢 ✓✓(33782 微秒)
===== 3. volatile 不保证原子性 =====
volatile int 期望 100000,实际 99803 —— 仍然丢失更新 ✗✗javap -v 看 monitor 指令 ✓;| # | 错误 | 说明 |
|---|---|---|
| 1 | volatile 计数器 ✗✗ | 实测丢 197 ✓✓ |
| 2 | volatile 修饰数组/集合元素 ✗✗ | 管不到元素 ✓ |
| 3 | 双检锁不加 volatile ✗✗ | 半成品实例 ✓✓ |
| 4 | 锁对象可变 ✗ | 监视器失效 ⚠️ |
| 5 | 锁内调外部方法 ✗✗ | 死锁不可控 ✓✓ |
验收方式:能解释「为什么实验 3 丢了 197 次」。
扩展任务:实现无锁的最近值缓存(volatile 引用 + 不可变快照)✓✓