线程安全指多线程并发访问时类总能表现正确行为,核心依赖原子性(操作不可分割)、可见性(修改及时被见)、有序性(指令不乱序)三者协同保障。

一个类是否线程安全,不能只看它“能不能跑起来”,关键要看它在多线程高并发下能否始终给出确定、一致、可预期的结果。Java中对线程安全的正式定义是:当多个线程访问某个类时,无论运行时环境如何调度、线程如何交替执行,只要调用方无需额外同步措施,该类总能表现出正确行为。这个“正确行为”的底层支撑,正是原子性、可见性、有序性三者协同保障的结果。
原子性:操作不被拆开,也不被插队
原子性解决的是“执行中途会不会被干扰”的问题。比如 i++ 看似一条语句,实际包含读值、加1、写回三步——若两个线程同时执行,就可能都读到旧值、都加1、都写回,最终只+1而非+2。
- 基础类型单次读/写(如
int a = 5;)默认具备原子性;但复合操作(++、+=、对象引用赋值除外)不是原子的 - 用
synchronized或ReentrantLock可以把一段逻辑打包成原子块 - 推荐优先使用
AtomicInteger、AtomicReference等原子类,它们基于CAS实现无锁原子更新,性能更优 - 注意:
volatile不能保证原子性,它只管“读写本身不拆”,不管“读-改-写”整套流程
可见性:改了就得让别人马上知道
可见性解决的是“我改了,你能不能立刻看到”的问题。由于JVM有工作内存与主内存之分,线程常缓存变量副本,导致一个线程更新后,另一个线程还在用旧值循环或计算。
- 典型表现:flag 控制循环的线程永远不退出(如
while(flag)中 flag 被另一线程设为 false 却不生效) -
volatile是最轻量级的可见性保障手段,它强制每次读取都从主内存加载、每次写入都立即刷回主内存 -
synchronized和Lock在加锁释放时,也隐含“清空本地缓存 + 刷新主内存”的语义,因此同样保证可见性 - 普通变量没有这些机制,修改可能长期滞留在某个线程的工作内存中
有序性:代码顺序 ≠ 执行顺序,但关键依赖不能乱
有序性解决的是“指令会不会被重排,导致逻辑错乱”的问题。编译器、JIT、CPU 都可能为优化性能调整指令顺序,只要不影响单线程结果。但在多线程下,这种重排可能破坏依赖关系。
- 常见陷阱:对象初始化未完成,引用就被其他线程拿到并使用(如
instance = new Singleton()可能被重排为“分配内存→设置引用→初始化对象”,导致拿到半初始化对象) -
volatile写操作具有“禁止指令重排序”的效果,能防止其前后的代码被重排到 volatile 写之后 -
synchronized块内指令不会被重排到块外,且进入/退出时遵循 happens-before 规则,天然保障有序性 - 双重检查锁(DCL)单例必须给 instance 加
volatile,否则无法阻止构造过程重排
判断一个类是否真正线程安全,就要逐项核验:共享状态的读写有没有竞态(原子性)、修改能否及时同步(可见性)、关键步骤是否存在被重排风险(有序性)。三者缺一不可,任一缺失都可能在压测或上线后偶然爆发,极难复现和定位。










