原子操作:Java 并发安全的基石
在多线程环境中,对共享变量的操作若不加以控制,可能导致数据不一致(如i++这类看似原子的操作,实际包含 “读取 - 修改 - 写入” 三步)。Java 的java.util.concurrent.atomic包提供了一系列原子操作类,通过底层的 CAS(Compare-And-Swap)机制,确保操作的原子性,避免线程安全问题。
原子操作类的核心原理
原子操作类的本质是通过Unsafe 类实现的 CAS 操作,其核心思想是:乐观锁—— 假设操作不会冲突,若冲突则重试,直到成功。以最常用的AtomicInteger为例,其内部通过volatile修饰的value存储值,并借助 Unsafe 的 native 方法实现原子更新。
AtomicInteger 的核心源码解析
1 | public class AtomicInteger extends Number implements java.io.Serializable { |
CAS 操作的底层逻辑
unsafe.compareAndSwapInt(this, valueOffset, expect, update)的工作流程:
- 从内存偏移量
valueOffset处读取当前值current; - 比较current与expect:
- 若相等,将
update写入该内存地址,返回true; - 若不等,不做操作,返回
false。
- 若相等,将
整个过程由 CPU 指令原子性执行,无需加锁,效率远高于synchronized。
原子操作类的常见类型与用法
java.util.concurrent.atomic包提供了多种原子类,覆盖基本类型、引用类型、数组等场景:
| 类型 | 代表类 | 用途 |
|---|---|---|
| 基本类型 | AtomicInteger |
原子更新 int 值 |
AtomicLong |
原子更新 long 值 | |
AtomicBoolean |
原子更新 boolean 值 | |
| 引用类型 | AtomicReference |
原子更新对象引用 |
AtomicStampedReference |
带版本号的原子引用(解决 ABA 问题) | |
| 数组类型 | AtomicIntegerArray |
原子更新 int 数组元素 |
| 字段更新器 | AtomicIntegerFieldUpdater |
原子更新对象的 int 字段(无需字段为 volatile) |
示例:使用 AtomicInteger 实现线程安全计数器
1 | public class AtomicCounter { |
原子操作的局限性与解决思路
1. ABA 问题
问题:若变量值从 A 变为 B,再变回 A,CAS 会误认为值未修改,导致更新错误。
解决:使用AtomicStampedReference,为值绑定一个版本号,更新时同时检查值和版本号。
1 | AtomicStampedReference<Integer> stampedRef = new AtomicStampedReference<>(100, 0); |
2. 高并发下的自旋开销
CAS 失败时会重试(自旋),高并发场景下可能导致大量线程空转,消耗 CPU 资源。
解决:
- 结合
LongAdder(JDK 8+):高并发时分散热点,通过多个单元格累加结果,降低冲突; - 合理控制并发粒度,避免过度竞争。
指令重排序对原子操作的影响
JVM 和 CPU 为优化性能,会对指令进行重排序(在不影响单线程语义的前提下调整执行顺序)。但原子操作依赖操作的有序性,若重排序破坏了执行顺序,可能导致逻辑错误。
重排序的类型
- 编译器重排序:编译器调整代码执行顺序;
- 指令级重排序:CPU 将多条指令并行执行,若无数据依赖则调整顺序;
- 内存重排序:由于缓存和读写缓冲区,加载 / 存储操作可能看似乱序。
原子操作如何避免重排序?
- volatile 的内存屏障:原子类中的
value字段用volatile修饰,其读写会插入内存屏障(如StoreLoad),禁止重排序; - CAS 的原子性:CAS 操作由 CPU 指令保证原子性,不会被重排序打断。