Skip to content

synchronized 与 volatile ​

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

1. 本节定位 ​

《并发基础:线程、锁与内存模型》 提出问题;本节是两种同步手段的分工:

text
synchronized:互斥 + 可见性(重,但全能)
volatile    :只有可见性(轻,但只读场景)

★ 本节实测(ConcurrencyLesson):

text
synchronized:10 万次自增一次不丢(3.4 万微秒)✓✓
volatile int :丢 197 次更新 ✗✗ —— 「volatile 计数器」是错的 ✓✓✓

2. 学习目标 ​

  • 能说清两者能力边界 ★★★;
  • 知道 volatile 的两个正当用途与一个大误区 ★★★;
  • 知道锁升级 ★★;
  • 会用场景矩阵选手段 ★★★。

3. 前置知识检查 ​

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

★ 实测已证明 ✗✗:volatile 计数器丢 197 次;synchronized 同规模一次不丢。 但 volatile 不是没用 ——它是「停止标志、配置开关、双检锁」的必需品 ✓✓

5. 核心概念 ​

5.1 能力矩阵 ★★★ ​

能力synchronizedvolatile
原子性(读改写)✓✗(实测丢 197)
可见性✓✓
禁止重排✓✓
阻塞会阻塞不阻塞 ✓
范围代码块/方法单个变量 ✗

★★★ 选择口诀:只写一次多方读 → volatile ✓;读改写 → 锁或原子类 ✓;多变量一起变 → 锁 ✓

5.2 volatile 的两个正当用途 ★★★ ​

  1. 状态标志 ★★★:停止标志/配置开关——一次写、多方读、无读改写 ✓✓
  2. 双检锁单例的 instance ★★★:防「分配-初始化-赋值」重排(否则别的线程拿到半成品)✗✗

★ 反例回顾 ✗✗:volatile 计数器(本节实测复现)

5.3 synchronized 的锁升级 ★★ ​

text
无锁 → 偏向锁 → 轻量级锁(CAS 自旋)→ 重量级锁(OS 互斥量)

JDK 15 起偏向锁已废弃 ✓;低竞争轻量级锁快 ✓;高竞争膨胀 ⚠️; 锁要粒度小、持有时间短 ✓✓(呼应 《Sentinel 限流与熔断》)

5.4 使用清单 ★★★ ​

场景手段
停止标志/开关 ★★★volatile boolean ✓
计数器/累加 ★★★AtomicLong / LongAdder ✓
复合不变量(多字段一起变)★★★synchronized ✓
单例 instance ★★volatile 双检锁,或静态内部类/枚举 ✓
读多写少的映射 ★★ConcurrentHashMap(《JUC 工具类与并发容器》)✓

6. 递进示例 ​

6.1 volatile 标志 ​

java
private static volatile boolean running = true;
public void shutdown() { running = false; }

6.2 双检锁单例 ​

java
private static volatile Instance instance;    // ★ 必须 volatile
public static Instance get() {
    if (instance == null) {
        synchronized (Instance.class) {
            if (instance == null) { instance = new Instance(); }
        }
    }
    return instance;
}

6.3 反例:volatile 计数器(实测复现 ✗✗) ​

java
private volatile int count;
void increment() { count++; }    // ✗✗ 可见性有了,原子性没有:实测丢 197 次更新
// 修复:synchronized increment() / AtomicInteger / LongAdder(高竞争最优)

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

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

实测输出:

text
===== 2. synchronized 修复 =====
  期望 100000,实际 100000 —— 一次都没丢 ✓✓(33782 微秒)
===== 3. volatile 不保证原子性 =====
  volatile int 期望 100000,实际 99803 —— 仍然丢失更新 ✗✗

8. Debug 观察点 ​

  1. 对比丢失数量(0 vs 197)✓✓;
  2. javap -v 看 monitor 指令 ✓;
  3. 换 AtomicLong 再跑 ✓。

9. 常见错误 ​

#错误说明
1volatile 计数器 ✗✗实测丢 197 ✓✓
2volatile 修饰数组/集合元素 ✗✗管不到元素 ✓
3双检锁不加 volatile ✗✗半成品实例 ✓✓
4锁对象可变 ✗监视器失效 ⚠️
5锁内调外部方法 ✗✗死锁不可控 ✓✓

10. 动手实验 ​

  1. 跑两个对照实验,记录丢失数量(0 vs 197)✓✓;
  2. 换 ReentrantLock(《Lock、CAS 与 AQS》)对比 ✓;
  3. 写双检锁单例并验证 ✓✓。

验收方式:能解释「为什么实验 3 丢了 197 次」。

扩展任务:实现无锁的最近值缓存(volatile 引用 + 不可变快照)✓✓

11. 练习题 ​

  1. 判断:volatile 能替代 synchronized。(对 / 错)
  2. 填空:volatile 保证 ______ 与禁止重排,不保证 ______。
  3. 简答:双检锁为什么必须 volatile?
  4. (选择)配置项启动加载、运行时可热更——用什么?
  5. (设计)多字段对象(x,y 一起更新)如何保证一致?
参考答案(要点)
  1. 错 ✗✗。volatile 无互斥,读改写会丢(实测 197)✓✓★
  2. 可见性;原子性 ✓
  3. new 是三步,重排后另一线程可能拿到未初始化的引用;volatile 禁止重排 ✓✓★
  4. volatile ✓(一次写多方读)✓★
  5. 不可变对象整体替换(volatile 引用指向不可变快照)✓✓;或 synchronized ✓★

12. 掌握标准 ​

  • [ ] 能说清两者能力边界 ★★★;
  • [ ] 知道 volatile 两个正当用途与计数器误区 ★★★;
  • [ ] 知道锁升级 ★★;
  • [ ] 会用场景矩阵选手段 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

《Lock、CAS 与 AQS》。

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

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