上一篇:生命周期与通信 · 下一篇:synchronized 与 Lock
三大特性
| 特性 | 含义 | 典型手段 |
|---|---|---|
| 原子性 | 操作不可被中途打断 | synchronized、Lock、Atomic、CAS |
| 可见性 | 一个线程修改对其他线程可见 | volatile、synchronized、Lock |
| 有序性 | 按代码顺序的感知 | happens-before、volatile 禁重排 |
i++ 不是原子操作(读-改-写三步)。
面试常问:synchronized 和 volatile 各解决什么问题?
| 解决什么 | 不解决什么 | |
|---|---|---|
| synchronized | 原子性 + 可见性 + 互斥(同一锁下代码块同时只有一个线程) | 性能开销、可能死锁 |
| volatile | 可见性 + 禁止部分重排序 | i++ 等复合操作的原子性 |
记忆:volatile 管「看得见」;synchronized 管「抢得到锁、改得对」。
→ 锁详解 synchronized 与 Lock。
Java 内存模型(JMM)
每个线程有 工作内存,共享变量在主内存。线程读写先拷贝到工作内存,写回主内存才对其他线程可见——中间窗口导致 可见性问题。
线程共享 vs 私有
| 共享 | 线程私有 |
|---|---|
| 堆、方法区(元空间) | 虚拟机栈、本地方法栈、程序计数器 |
每个线程有自己的栈;堆上对象可被多线程共享,需同步保护。
volatile 两大作用
- 可见性:写立即刷主内存,读从主内存取最新值
- 禁止指令重排序(内存屏障语义),保证部分 happens-before
不能保证复合操作原子性(如 volatile int 的 ++ 仍不安全)。
volatile vs synchronized
| volatile | synchronized | |
|---|---|---|
| 原子性 | 仅单次读/写 | 保证临界区 |
| 阻塞 | 不阻塞 | 可能阻塞 |
| 适用 | 状态标志、DCL 单例的实例引用 | 复合操作、互斥 |
happens-before 规则(节选)
- 程序顺序:同线程前序 happens-before 后序
- 监视器锁:unlock happens-before 后续 lock
- volatile:写 happens-before 后续读
- 线程 start:start happens-before 线程内动作
- 线程 join:线程内动作 happens-before join 返回
指令重排
编译器/CPU 为优化可能重排指令。单线程 as-if-serial 结果不变;多线程下可能暴露 bug。volatile / synchronized 限制重排范围。
乐观锁 vs 悲观锁
- 悲观锁:认为冲突常发生,先加锁再操作(synchronized、Lock)
- 乐观锁:认为冲突少,CAS 比较-交换,失败重试
不可变对象
final 字段正确发布后不可变,读无需额外同步,利于并发安全(如 String、不可变 DTO)。
双重检查锁单例(DCL)
public class Singleton { private static volatile Singleton instance;
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 需 volatile 防重排 } } } return instance; }}无 volatile 时,其他线程可能看到未完全初始化的对象。
一句话总结
并发 bug 多来自可见性、有序性、原子性三者之一;volatile 管可见性和部分有序性,复合操作要用锁或 Atomic。