多线程安全需兼顾可见性、原子性、有序性:volatile保可见性(如状态标志),synchronized/Lock保原子性及附带可见有序性,final+volatile+happens-before规则保有序性,复杂计数优先用AtomicXXX。

要让多线程代码真正安全可靠,关键不是堆砌工具,而是针对每条原则选对机制、用对场景。可见性解决“改了别人看不着”,原子性解决“改一半被抢走”,有序性解决“代码写着A-B-C,实际跑成C-A-B”。三者缺一不可,但也不能滥用。
用 volatile 保可见性,但别指望它管原子
当一个变量只被多个线程读写,且写操作是简单赋值(比如开关标记、状态标志),就用 volatile。它强制每次读都从主内存取,每次写都立刻刷回主内存,并禁止相关指令重排序。
常见误用:给 count++ 加 volatile——没用。因为 ++ 是“读-改-写”三步,volatile 只保证每一步的读写可见,不保证三步整体不被其他线程插队。
- ✅ 正确用法:
private static volatile boolean running = true;配合 while(running) 做线程终止控制 - ✅ 正确用法:
private volatile int status = 0;表示初始化完成、已就绪等单次写入状态 - ❌ 错误用法:
private volatile int counter = 0;然后多线程调counter++
用 synchronized 或 Lock 保原子性,顺便捎带可见性和有序性
只要涉及复合操作(i++、list.add()、map.put())、多个变量协同更新(如先改 A 再改 B)、或需要互斥临界区,就必须上锁。synchronized 和 ReentrantLock 不仅能串行化执行,还天然提供“解锁前刷主存、加锁前清本地缓存”的语义,因此也保障了可见性和有序性。
立即学习“Java免费学习笔记(深入)”;
- ✅ 小范围临界区优先用 synchronized 块:比方法粒度细,性能更好
- ✅ 需要超时、中断、条件等待时,选 ReentrantLock
- ✅ 多个共享变量一起修改时,确保所有读写都在同一把锁下,否则仍可能出错
用 final + volatile + happens-before 规则守住有序性
有序性不是靠“禁止所有重排”,而是靠建立“先行发生(happens-before)”关系,让 JVM 和 CPU 知道哪些顺序必须保留。最常用手段有:
- volatile 写 → volatile 读:前者的写操作对后者可见,且其之前的指令不会重排到后者之后
- 解锁 → 加锁:前一个 synchronized 块的解锁,happens-before 后一个同锁的加锁
- 构造结束 → start():对象构造完成并赋值给引用后,再调用 thread.start(),新线程能看到构造结果
- final 字段的语义:正确构造的对象中,final 字段一旦初始化完成,其他线程看到该对象引用时,就能看到 final 字段的正确值(无需额外同步)
典型陷阱:对象发布未同步。比如在构造器里启动线程,又没用 volatile 或安全发布方式(如静态工厂+final),新线程可能看到部分初始化的对象。
复杂计数/累加优先用 AtomicXXX,而不是自己锁或 volatile
对 int、long、引用等类型的无锁原子操作,AtomicInteger、AtomicReference、LongAdder(高并发累加场景更优)是标准解法。它们底层基于 CAS + Unsafe,比 synchronized 更轻量,且明确提供原子性保证。
- ✅ 计数器、版本号、状态位更新,首选 AtomicXXX
- ✅
compareAndSet(expected, new)适合带条件的更新逻辑 - ✅
LongAdder在高竞争场景下比AtomicLong性能好得多
不复杂但容易忽略



















