在 Java 并发编程中,可见性、有序性、原子性是并发错误的三大核心根源。 其中 JMM (Java内存模型)用来解决 可见性 和 有序性
一、如何解决 可见性问题?
问题回顾:
- 一个线程修改了共享变量,其他线程看不到(缓存没有刷新)。
🔧 Java 解决方式:
| 机制 | 说明 |
|---|---|
volatile |
禁止线程缓存变量,修改立即写入主内存;读取强制从主内存读。 ✅轻量方式 |
synchronized |
加锁会刷新工作内存,进入同步块前读取主内存,退出时写回主内存 |
final |
构造函数安全发布(对象构造完成前不能被其他线程看到) |
| JMM 的 happens-before 规则 | Java 的内存模型定义了一系列可见性保证规则 |
二、如何解决 有序性问题?
问题回顾:
- JVM 和 CPU 会重排序指令,导致多线程执行顺序不一致。
🔧 Java 解决方式:
| 机制 | 说明 |
|---|---|
volatile |
禁止重排序(对读写操作的指令不会和前后的指令重排序) |
synchronized |
锁具备内存屏障效果,进入和退出同步块都包含“顺序约束” |
happens-before 语义 |
JMM 规定了某些操作之间的“先发生于”关系,编译器/CPU 会保持顺序 |
📝 举例(重排序引发的问题):
1
2
3
4
5
6
7
8
9
10
11
int a = 0;
boolean flag = false;
Thread A:
a = 1;
flag = true; // 可能重排序到前面
Thread B:
if (flag) {
System.out.println(a); // 可能输出 0
}
使用
volatile flag可禁止重排序。
三、如何解决 原子性问题?
问题回顾:
- 多线程并发修改变量时,中间过程被打断,导致结果错误。
🔧 Java 解决方式:
| 机制 | 说明 |
|---|---|
synchronized |
让某段代码在任意时刻只允许一个线程执行,从而保证操作的原子性 |
ReentrantLock |
可替代 synchronized,有更强的功能(可中断、公平锁、tryLock) |
AtomicInteger 等原子类 |
JDK 提供的原子操作类,底层基于 CAS,可高性能地支持线程安全的加减等操作 |
数据结构并发容器(如 ConcurrentHashMap) |
内部采用分段锁或CAS原语,支持高性能并发访问 |
总结
| 并发问题 | 典型现象 | Java 解决机制 |
|---|---|---|
| 可见性 | 线程 A 修改变量,线程 B 看不到 | volatile、synchronized、final、JMM happens-before |
| 有序性 | 指令被重排序,行为反直觉 | volatile、synchronized、JMM 顺序规则 |
| 原子性 | 多线程修改共享变量,结果错误 | synchronized、Lock、AtomicXXX、并发容器 |
