八股-线程安全
线程安全
线程安全的核心是:在多线程并发访问共享资源(变量、对象、文件等)时,程序的执行结果始终符合预期,且不会出现数据错乱或逻辑错误。
其根源在于以下三个层面的问题:
- 原子性:操作是否不可分割。要么全部执行成功,要么全部不执行,不存在中间状态被其他线程看到。
- 可见性:线程对共享变量的修改是否对其他线程立即可见。由于 CPU 缓存和寄存器的存在,一个线程的修改可能不会马上同步到主内存。
- 有序性:代码执行顺序是否可能被编译器和 CPU 重排序。在保证单线程结果不变的前提下,指令可能会被优化重排,导致多线程下执行顺序与代码顺序不一致。
i++ 问题的深入分析
i++ 虽然只有一行代码,但它并不是原子性操作。
字节码层面分析
编译后会被拆分为四条 JVM 字节码指令:
1 | getstatic i // 读取变量 i 的值到操作数栈 |
竞态条件示例
多线程环境下,线程切换可能发生在任意字节码指令之间,导致操作被”切割”。
假设线程 A 和线程 B 同时执行 i++(初始值 i = 0):
- 时刻 1:线程 A 执行
getstatic读取到 0,线程 B 执行getstatic也读取到 0 - 时刻 2:线程 A 执行
iadd计算得 1;线程 B 执行iadd计算也得 1 - 时刻 3:线程 A 执行
putstatic写回 1;线程 B 执行putstatic也写回 1
最终结果 i = 1,而期望值是 2,出现数据丢失(Race Condition)。
解决方案
- synchronized:使用同步代码块或方法,保证
i++操作的原子性 - Lock:使用
ReentrantLock等显式锁进行同步 - 原子类:使用
AtomicInteger的incrementAndGet()方法,基于 CAS 无锁算法实现原子递增