八股-线程安全

线程安全

线程安全的核心是:在多线程并发访问共享资源(变量、对象、文件等)时,程序的执行结果始终符合预期,且不会出现数据错乱或逻辑错误。

其根源在于以下三个层面的问题:

  • 原子性:操作是否不可分割。要么全部执行成功,要么全部不执行,不存在中间状态被其他线程看到。
  • 可见性:线程对共享变量的修改是否对其他线程立即可见。由于 CPU 缓存和寄存器的存在,一个线程的修改可能不会马上同步到主内存。
  • 有序性:代码执行顺序是否可能被编译器和 CPU 重排序。在保证单线程结果不变的前提下,指令可能会被优化重排,导致多线程下执行顺序与代码顺序不一致。

i++ 问题的深入分析

i++ 虽然只有一行代码,但它并不是原子性操作。

字节码层面分析

编译后会被拆分为四条 JVM 字节码指令:

1
2
3
4
getstatic i    // 读取变量 i 的值到操作数栈
iconst_1 // 将常量 1 压入操作数栈
iadd // 栈顶两个值相加(i + 1)
putstatic 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 等显式锁进行同步
  • 原子类:使用 AtomicIntegerincrementAndGet() 方法,基于 CAS 无锁算法实现原子递增