java线程安全问题根源在于jmm中主内存与工作内存分离、cpu/编译器指令重排序及count++等操作非原子性,导致可见性、原子性、有序性三大问题;volatile解决前两者,synchronized三者兼顾。

Java 线程安全问题,不能只看“多个线程改同一个变量”这个表象。真正要讲清楚,得钻进 Java 内存模型(JMM)和 CPU 指令执行的底层逻辑里——它本质上是主内存、工作内存、编译器与处理器三方“各干各的”,又没及时对齐造成的。
Java 内存模型:每个线程都有自己的“小黑板”
在 JMM 中,所有共享变量都存在主内存(Main Memory),而每个线程都有自己独立的工作内存(Working Memory),相当于 CPU 的寄存器或高速缓存(L1/L2/L3)。线程读写变量必须走这个路径:
- 读:先从主内存把变量值拷贝到自己工作内存中,再操作
- 写:修改完工作内存后,择机(不保证立即)刷回主内存
- 其他线程看不到你工作内存的改动,除非它重新从主内存加载
这就导致了**可见性问题**:t1 把 count = 100 写回主内存前被挂起,t2 仍读到旧值 99,两个线程各自 +1 后都写回 100,最终只加了一次。
count++ 不是原子操作:三步拆开就容易断
表面上一行代码 count++,在底层对应三个不可分割的机器级动作:
- load:从主内存/工作内存读取 count 值到 CPU 寄存器
- add:寄存器中值加 1
- save:把结果写回工作内存,并(可能延迟)同步到主内存
由于操作系统线程调度是抢占式的,这三个步骤随时可能被中断。比如 t1 执行完 load(读到 100)就被切走,t2 完成 load→add→save 全流程把 count 变成 101;等 t1 回来继续 add(100+1=101)再 save,结果仍是 101——**丢失一次更新**。
指令重排序:编译器和 CPU 的“优化陷阱”
为了提升性能,编译器和处理器会在不改变单线程语义的前提下,调整指令执行顺序。例如以下代码:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
boolean ready = false;
int data = 0;
// 线程 A
data = 42;
ready = true;
// 线程 B
if (ready) {
System.out.println(data);
}
单看逻辑,data 赋值在 ready 之前,但编译器可能把 ready = true 提前执行。线程 B 看到 ready == true,却读到未初始化的 data(0 或随机值)。这不是 bug,是 JMM 允许的重排序——除非用 volatile 或锁建立 happens-before 关系,强制约束执行顺序。
volatile 和 synchronized 怎么破局
它们不是“魔法”,而是通过 JMM 规则干预底层行为:
- volatile:给变量加两道锁——写 volatile 变量时,强制把工作内存中所有先前修改刷回主内存;读 volatile 变量时,强制丢弃工作内存副本,重新从主内存加载。同时禁止编译器和 CPU 对其前后指令重排序。
- synchronized:进入同步块时,清空工作内存中该锁保护的所有变量副本(下一次读必须从主内存加载);退出时,把工作内存中所有修改强制刷回主内存。还天然提供互斥和 happens-before 保证。
二者分工明确:volatile 解决可见性 + 有序性,但不管原子性;synchronized 连带解决可见性、原子性、有序性三重问题。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










