REJAVA / LEARNING NOTES
速查:JMM(Java 内存模型) · volatile(可见性关键字) · synchronized(内置同步关键字)
随时回查术语、规则和示例;完整解释见对应课程。
根据课程材料整理
本次更新说明
- 补充 Executor(执行器接口)的中文释义;改写结尾的复习提示。
- 整理为公开学习笔记,保留技术内容与练习。
- 清理个人分析,完善阅读导航。
三大特性
| 特性 | 一句话 | 谁保证 |
|---|---|---|
| 复合操作原子性 | 操作不可分割;用锁保护复合操作时,相关访问须遵守同锁协议,异常不自动回滚 | synchronized(内置同步关键字)、Lock(显式锁接口);原子类的指定操作 |
| 可见性 | 同步关系成立时,先前写入对后续读取可见 | volatile(可见性关键字)、synchronized(内置同步关键字)、Lock(显式锁接口) |
| 有序性 | 并发观察结果遵守内存模型的顺序约束 | volatile(可见性关键字,约束相关重排)、synchronized(内置同步关键字) |
JMM(Java 内存模型)
- 一组 规范 :抽象线程与主内存的关系,规定源码到 CPU(处理器) 指令要遵守的并发原则
- 主内存、本地内存都是 规范层面的抽象 (不与某块物理内存一一对应)
- ≠ JVM(Java 虚拟机) 内存区域 :内存区域(堆/栈/方法区)讲「数据放哪」
- happens-before(先行发生关系) :A 的结果对 B 可见, 不是 时间先后
happens-before(先行发生关系) 常用规则
- 程序顺序 :同一线程内,前面的 hb 后面的
- 监视器锁 :解锁 hb 随后对 同一个 监视器的加锁
- volatile(可见性关键字) :写 hb 随后对同一变量的读
- 线程启动 :
start()hb 新线程里的每个动作 - 线程终止 :线程的所有动作先行发生于另一方确认其终止;例如无超时的
join()正常返回 - 传递性 :A hb B,B hb C ⇒ A hb C
另有线程中断等规则;并发容器、Executor(执行器接口)、Future(异步结果句柄) 也提供 hb 保证(JDK 文档)。
volatile(可见性关键字)
- 可见性 :对同一变量的写先行发生于后续读,连带发布前的写入可见
- 禁止重排序 :约束与该变量读写相关的重排序,不是禁止所有重排序
- 不保证复合操作原子性 :
i++仍然不安全 - 屏障(理解用的模型,JVM(Java 虚拟机) 可合并或省略):写前 StoreStore(写写屏障)、写后 StoreLoad(写读屏障);读后 LoadLoad(读读屏障) + LoadStore(读写屏障)
- DCL(双重检查锁定) :防「初始化」和「引用赋值」之间的重排;第一次检查在锁外,所以必须加
synchronized(内置同步关键字):用法与底层
- 实例方法 →
this(当前实例);静态方法 → 该类的Class(类对象类型) 对象;代码块 → 括号表达式指向的对象;不能修饰变量 - 互斥只约束竞争 同一对象监视器 的线程,不能阻止不拿这把锁的线程访问字段
- 静态与实例同步方法默认使用不同监视器;方法内若另用共同锁,仍需单独判断。 可重入 指当前持有者再次获取同一监视器
- 代码块:
monitorenter(进入监视器指令) 与monitorexit(退出监视器指令);简单示例含正常与异常释放路径,数量不是固定规则 - 方法:
ACC_SYNCHRONIZED(同步方法访问标志) 标志;两者本质都是获取对象的监视器 wait/notify必须持有该对象的监视器,否则IllegalMonitorStateException
volatile(可见性关键字) vs synchronized(内置同步关键字)
| volatile(可见性关键字) | synchronized(内置同步关键字) | |
|---|---|---|
| 用在哪 | 字段,不用于局部变量 | 方法、代码块 |
| 复合操作原子性 | ✗ | 相关访问遵守同一锁协议时保证 |
| 可见性 | ✓ volatile(可见性关键字) 规则 | ✓ 监视器锁规则 |
| 会阻塞 | 不会 | 竞争时会 |
| 怎么选 | 只要可见性(标志位、DCL(双重检查锁定) 引用) | 要复合操作原子性(i++、先检查后执行) |
两者互补:volatile(可见性关键字) 读写有类似进出监视器的内存效果,但不互斥(JDK 文档)。
锁状态 × JDK 版本(2026 必须按新说法答)
| JDK | 偏向锁 | synchronized(内置同步关键字) 可能的锁状态 |
|---|---|---|
| 8 / 11 | 默认开启 | 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 |
| 15 | 默认关闭 + 标记废弃(JEP 374) | — |
| 17 | 默认关闭;手动开启仍生效,但有废弃警告 | 默认:无锁 → 轻量级锁 → 重量级锁 |
| 18 | 彻底移除,命令行打不开 | — |
| 21 / 25 | 参数不认:带上就 Unrecognized VM option,启动失败 | 无锁 → 轻量级锁 → 重量级锁 |
- 轻量级锁 :竞争很轻时用 CAS(比较并交换) 加锁,不让操作系统挂起线程; 重量级锁 :依赖操作系统互斥量,抢不到就挂起(要切到内核态)
- 为什么去掉偏向锁 :现代应用收益下降,竞争时撤销有额外成本;JVM(Java 虚拟机) 实现复杂、维护与演进困难。见 JEP 374 官方存档。
- 升级坑 :
-XX:+UseBiasedLocking、-XX:BiasedLockingStartupDelay=0在 JDK 17 只警告,在 JDK 21 直接启动失败(本机实测) - 虚拟线程(后续专题) :JDK 21 上在 synchronized(内置同步关键字) 里阻塞会钉住载体线程;本机 JDK 25 实测已不再出现
一句话总结
- volatile(可见性关键字) :「写 hb 后续读,约束相关重排序;不保证复合操作原子性。」
- synchronized(内置同步关键字) 原理 :「代码块 monitorenter(进入监视器指令)/monitorexit(退出监视器指令),方法 ACC_SYNCHRONIZED(同步方法访问标志),本质都是获取对象的监视器。」
- synchronized(内置同步关键字) 可见性 :「解锁 hb 随后对同一个监视器的加锁。」
- DCL(双重检查锁定) :「synchronized(内置同步关键字) 只保护拿同一把锁的线程;第一次检查在锁外,所以还要 volatile(可见性关键字)。」
- 锁升级 :「JDK 8 是四种状态;偏向锁 15 默认关、18 移除;17 默认关闭但可显式启用;21 已移除。」
常见错误说法
- volatile(可见性关键字) 禁用 CPU(处理器) 缓存、每次读写物理主存
可见性是 happens-before(先行发生关系) 语义保证 - happens-before(先行发生关系) 就是时间上先发生
是「结果对后者可见」 - 主内存就是物理内存条
是规范层面的抽象 - JDK 17/21 上锁升级要经过偏向锁
偏向锁已默认关闭 / 移除 - DCL(双重检查锁定) 有 synchronized(内置同步关键字) 就够了
第一次检查在锁外,必须加 volatile(可见性关键字) - 静态同步方法和实例同步方法互斥
仅靠方法默认同步时锁对象不同;若内部另有共同锁,须按实际路径判断
出处: JavaGuide《JMM(Java 内存模型) 详解》《Java 并发常见面试题总结(中)》、JDK 21 官方文档(java.util.concurrent 包说明、Object.notify()、java 命令手册)、本机实验记录。逐条引用见 第 3 课、第 4 课;存档在 reference/ 目录。
哪一条读着没把握,就回对应的课重看。