要真正掌握Java并发编程的三大特性,必须将其锚定在Java内存模型(JMM)结构上:原子性对应JMM的8种原子操作边界,可见性依赖主内存与工作内存同步机制,有序性由内存屏障和happens-before规则约束。

要真正掌握 Java 并发编程的三大特性——原子性、可见性、有序性,不能孤立记忆概念,必须把它们“锚定”在 Java 内存模型(JMM)的结构上。JMM 不是物理内存布局,而是一套关于线程如何读写共享变量的抽象规则。三大特性本质上就是 JMM 为解决线程间通信问题所定义的三类保障目标。
原子性:从“单步不可分”到 JMM 操作指令的边界
原子性关注的是“执行过程不被中断”。JMM 明确定义了底层的 8 种原子操作(read、load、use、assign、store、write、lock、unlock),所有 Java 代码的变量读写最终都映射到这些动作上。比如 a = 1 对应 assign(工作内存赋值),而 a++ 至少涉及 read→use→assign→store→write 五步,中间可被其他线程插入操作。
理解这一点就能明白为什么:
- 32 位 JVM 上对 long/double 的非 volatile 赋值可能不是原子的(需两次 write)
- volatile 只能保证单次读/写的原子性,无法覆盖复合操作
- synchronized 和 Lock 的本质,是用 lock/unlock 操作将多步指令“包裹”成一个不可分割的临界区
可见性:JMM 中“主内存 ↔ 工作内存”的同步机制
可见性失效的根本原因,是线程把变量缓存在自己的工作内存(对应 CPU 缓存),修改后未及时刷回主内存,或读取前未主动更新本地副本。JMM 规定了哪些操作会触发同步:
立即学习“Java免费学习笔记(深入)”;
- volatile 写:写入后强制 store+write,并插入写屏障,使其他线程的缓存失效
- synchronized 释放锁:隐式执行 unlock,要求将工作内存中所有共享变量刷新回主内存
- final 字段构造结束:对象发布时,确保 final 值对其他线程可见(happens-before 保证)
没有这些机制,就像两个程序员各自编辑同一份文档却不点“同步”,改了也白改。
有序性:重排序的边界由内存屏障和 happens-before 划定
JMM 允许编译器和处理器重排序,但前提是不改变单线程语义。多线程下,重排序可能破坏逻辑——比如 DCL 单例中 new Singleton() 的三步(分配内存→初始化→赋值给引用)若被重排为 分配→赋值→初始化,另一个线程就可能拿到未完成构造的对象。
JMM 通过两种方式约束重排序:
- volatile:在读操作前加读屏障,写操作后加写屏障,禁止指令越过屏障重排
- happens-before:提供一套高级规则(如监视器锁规则、volatile 变量规则、程序顺序规则),只要满足任一规则,前操作的结果就对后操作可见,且禁止重排序
换句话说,有序性不是“禁止一切重排”,而是“在关键路径上设置不可逾越的栅栏”。
把三者串起来:用 JMM 看懂一个典型场景
以 volatile boolean flag 控制线程启停为例:
- 可见性:flag 被 volatile 修饰,线程 B 修改它时,强制刷新到主内存;线程 A 读取时,必须从主内存加载最新值
- 有序性:volatile 写之前的代码,不会被重排到写之后;volatile 读之后的代码,不会被重排到读之前——这保证了 flag 读写与其他操作的逻辑顺序
- 原子性:flag 的读和写本身是原子的(boolean 是基本类型),但 flag = false 这个赋值动作不包含其他业务逻辑,所以无需额外保障
如果换成 flag = true && doWork(),那就要用 synchronized 把整个块包住——此时你是在用锁同时解决三者:原子性(临界区)、可见性(解锁刷新)、有序性(锁内串行执行)。



















