在 java.util.concurrent.atomic 包中,除了最常用的 AtomicInteger,还有一系列针对不同场景设计的原子类,涵盖 基本类型、引用类型、字段更新、复合操作 等,核心均基于 CAS + volatile 实现无锁原子性。以下是 常用原子类分类详解,附功能、场景和示例代码:
一、基本类型原子类(替代基本类型的原子操作)
与 AtomicInteger 原理一致,针对 long、boolean 等基本类型,提供原子增减、赋值等操作,避免非原子操作(如 long++)的并发问题。
| 原子类 | 核心功能 | 适用场景 | 关键方法 |
|---|---|---|---|
AtomicLong |
长整型的原子增减、累加、赋值 | 大数值计数器(如接口调用量、流量统计) | incrementAndGet()、addAndGet(long delta)、set(long) |
AtomicBoolean |
布尔值的原子切换(true/false) | 状态标记(如 "是否初始化完成""是否停止") | compareAndSet(boolean expect, boolean update)、set(boolean) |
AtomicByte/AtomicShort/AtomicChar |
字节、短整型、字符的原子操作 | 小范围数值原子更新(如状态码、字符标识) | incrementAndGet()、getAndSet(byte newValue) |
示例:AtomicLong 统计接口调用量
java
运行
java
import java.util.concurrent.atomic.AtomicLong;
// 接口调用量统计(高并发下原子计数)
public class ApiCounter {
private final AtomicLong callCount = new AtomicLong(0);
// 原子自增:每次调用接口时执行
public void incrementCallCount() {
callCount.incrementAndGet(); // 等价于 callCount++(原子操作)
}
// 原子累加:批量统计时使用
public void addCallCount(long batch) {
callCount.addAndGet(batch); // 等价于 callCount += batch(原子操作)
}
// 获取当前调用量
public long getCallCount() {
return callCount.get();
}
}
注意事项:
AtomicBoolean无incrementAndGet()方法,需通过compareAndSet实现状态切换(如while (!atomicBoolean.compareAndSet(false, true)) {})。- 基本类型原子类均不支持
null值,初始值默认是 0(数值类型)、false(AtomicBoolean)。
二、引用类型原子类(原子更新对象引用)
针对 对象引用 的原子操作,解决 "原子替换对象""避免引用修改冲突" 等问题,支持泛型,灵活适配各类对象。
| 原子类 | 核心功能 | 适用场景 | 核心解决问题 |
|---|---|---|---|
AtomicReference<V> |
引用类型的原子赋值、比较替换 | 原子更新对象实例(如单例对象、配置对象) | 避免多线程同时修改引用导致的对象不一致 |
AtomicStampedReference<V> |
带版本号的引用原子类(版本戳 + 引用) | 避免 CAS 中的 ABA 问题(如链表操作、缓存更新) | 解决 "值被修改后又改回原值" 导致的误判 |
AtomicMarkableReference<V> |
带标记位的引用原子类(布尔标记 + 引用) | 只需判断引用是否被修改过(无需版本号) | 简化版 ABA 问题(仅需 "是否修改" 标记) |
1. AtomicReference 示例:原子更新配置对象
java
运行
arduino
import java.util.concurrent.atomic.AtomicReference;
// 原子更新配置对象(避免多线程修改配置时出现部分更新)
public class AtomicConfig {
// 配置对象(不可变类,修改时直接替换整个对象)
static class Config {
private final String url;
private final int timeout;
public Config(String url, int timeout) {
this.url = url;
this.timeout = timeout;
}
// getter 方法(无 setter,保证不可变)
public String getUrl() { return url; }
public int getTimeout() { return timeout; }
}
private final AtomicReference<Config> configRef = new AtomicReference<>(
new Config("http://default.com", 5000) // 初始配置
);
// 原子更新配置(替换整个 Config 对象)
public void updateConfig(String newUrl, int newTimeout) {
configRef.set(new Config(newUrl, newTimeout)); // 原子赋值
}
// 比较并替换配置(仅当当前配置是预期值时才更新)
public boolean compareAndUpdateConfig(Config expect, Config update) {
return configRef.compareAndSet(expect, update);
}
public Config getConfig() {
return configRef.get();
}
}
2. AtomicStampedReference 示例:解决 ABA 问题
java
运行
csharp
import java.util.concurrent.atomic.AtomicStampedReference;
// 用 AtomicStampedReference 避免 ABA 问题
public class ABAExample {
public static void main(String[] args) {
// 初始值:"A",版本戳:0
AtomicStampedReference<String> stampedRef = new AtomicStampedReference<>("A", 0);
// 线程1:尝试将 "A" 改为 "C"(预期版本 0)
new Thread(() -> {
int stamp = stampedRef.getStamp(); // 获取当前版本戳:0
System.out.println("线程1:当前值=" + stampedRef.getReference() + ", 版本=" + stamp);
// 模拟延迟(让线程2先执行 ABA 操作)
try { Thread.sleep(1000); } catch (InterruptedException e) {}
// 比较并替换:预期值 "A",预期版本 0,更新值 "C",新版本 1
boolean success = stampedRef.compareAndSet("A", "C", stamp, stamp + 1);
System.out.println("线程1:更新是否成功?" + success); // 输出 false(版本已变)
}).start();
// 线程2:执行 ABA 操作(A → B → A)
new Thread(() -> {
int stamp = stampedRef.getStamp(); // 版本 0
// 1. A → B(版本 0 → 1)
stampedRef.compareAndSet("A", "B", stamp, stamp + 1);
System.out.println("线程2:A → B,新版本=" + stampedRef.getStamp());
// 2. B → A(版本 1 → 2)
stamp = stampedRef.getStamp();
stampedRef.compareAndSet("B", "A", stamp, stamp + 1);
System.out.println("线程2:B → A,新版本=" + stampedRef.getStamp());
}).start();
}
}
- 结果:线程 1 延迟后尝试更新时,版本戳已从 0 变为 2,
compareAndSet失败,避免了 ABA 漏洞。
注意事项:
AtomicStampedReference的版本戳是int类型(可循环,但需避免频繁溢出),AtomicMarkableReference的标记位是boolean(仅需判断 "是否修改")。- 引用类型原子类更新的是 "引用本身",而非对象内部的字段(若需更新对象字段,用字段更新器原子类)。
三、字段更新器原子类(原子更新对象字段)
无需修改原有类的代码(无需让字段成为 volatile 或原子类),通过 反射机制 原子更新对象的实例字段或静态字段,适用于 "无法修改原有类" 的场景(如第三方库类)。
| 原子类 | 核心功能 | 适用场景 | 关键限制 |
|---|---|---|---|
AtomicIntegerFieldUpdater |
原子更新对象的 int 类型字段 |
第三方类的 int 字段原子增减 |
字段必须是 volatile(否则报错)、非 final |
AtomicLongFieldUpdater |
原子更新对象的 long 类型字段 |
第三方类的 long 字段原子累加 |
同上 |
AtomicReferenceFieldUpdater<V, T> |
原子更新对象的引用类型字段 | 第三方类的引用字段原子替换 | 字段必须是 volatile、非 final、非 private |
示例:AtomicIntegerFieldUpdater 更新第三方类字段
java
运行
csharp
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
// 第三方类(无法修改源码,字段是 volatile int)
class ThirdPartyClass {
volatile int count; // 必须是 volatile(否则更新器无法工作)
public ThirdPartyClass(int count) {
this.count = count;
}
public int getCount() {
return count;
}
}
// 用字段更新器原子操作第三方类的 count 字段
public class FieldUpdaterExample {
// 1. 创建字段更新器:指定目标类、字段名
private static final AtomicIntegerFieldUpdater<ThirdPartyClass> COUNT_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ThirdPartyClass.class, "count");
public static void main(String[] args) {
ThirdPartyClass obj = new ThirdPartyClass(10);
// 2. 原子自增(count = 10 → 11)
COUNT_UPDATER.incrementAndGet(obj);
System.out.println(obj.getCount()); // 输出 11
// 3. 原子累加(count = 11 → 14)
COUNT_UPDATER.addAndGet(obj, 3);
System.out.println(obj.getCount()); // 输出 14
// 4. 比较并替换(预期 14 → 更新为 20)
boolean success = COUNT_UPDATER.compareAndSet(obj, 14, 20);
System.out.println("替换是否成功?" + success); // 输出 true
System.out.println(obj.getCount()); // 输出 20
}
}
注意事项:
- 目标字段必须是
volatile(保证可见性,否则 JVM 会抛出IllegalArgumentException)。 - 字段不能是
final(final 字段不可修改,更新器无法操作)。 - 字段访问权限:若字段是
private,更新器无法通过反射访问(需字段是public、protected或同一包下的默认访问权限)。
四、数组类型原子类(原子更新数组元素)
针对数组元素的原子操作,支持 int、long、reference 类型数组,避免多线程修改数组元素时的并发冲突。
| 原子类 | 核心功能 | 适用场景 | 关键方法 |
|---|---|---|---|
AtomicIntegerArray |
原子更新 int[] 数组的元素 |
原子计数器数组、分布式 ID 生成器数组 | getAndIncrement(int index)、compareAndSet(int index, int expect, int update) |
AtomicLongArray |
原子更新 long[] 数组的元素 |
大数据量统计数组(如分桶计数) | 同上(参数为 long 类型) |
AtomicReferenceArray<V> |
原子更新引用类型数组的元素 | 原子更新对象数组(如缓存数组) | set(int index, V newValue)、compareAndSet(...) |
示例:AtomicIntegerArray 分桶原子计数
java
运行
java
import java.util.concurrent.atomic.AtomicIntegerArray;
// 分桶原子计数器(将统计对象按哈希分桶,提升并发性能)
public class AtomicArrayCounter {
private final AtomicIntegerArray bucketCounts;
private final int bucketSize; // 分桶数量
public AtomicArrayCounter(int bucketSize) {
this.bucketSize = bucketSize;
this.bucketCounts = new AtomicIntegerArray(bucketSize); // 初始值全为 0
}
// 根据 key 的哈希值选择分桶,原子自增
public void increment(String key) {
int bucketIndex = Math.abs(key.hashCode()) % bucketSize;
bucketCounts.getAndIncrement(bucketIndex); // 原子自增对应桶的计数
}
// 统计所有桶的总计数
public int getTotalCount() {
int total = 0;
for (int i = 0; i < bucketSize; i++) {
total += bucketCounts.get(i);
}
return total;
}
public static void main(String[] args) {
AtomicArrayCounter counter = new AtomicArrayCounter(10); // 10个分桶
// 多线程并发计数
for (int i = 0; i < 1000; i++) {
new Thread(() -> counter.increment("key-" + Math.random())).start();
}
// 等待所有线程执行完成
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("总计数:" + counter.getTotalCount()); // 输出约 1000(原子性保证准确)
}
}
注意事项:
- 数组长度固定(创建后无法扩容),需提前规划分桶数量(如根据并发量设置合理桶数,减少冲突)。
- 数组元素的原子操作仅针对 "单个索引",多个索引的操作不保证原子性(如同时修改索引 0 和 1,需额外同步)。
五、复合操作原子类(JDK 8+ 新增,支持复杂原子逻辑)
JDK 8 引入的 DoubleAccumulator、LongAccumulator 等,支持 自定义复合原子操作 (如累加、最大值计算、自定义函数),性能比 AtomicLong 更优(高并发下分散竞争)。
| 原子类 | 核心功能 | 适用场景 | 核心优势 |
|---|---|---|---|
LongAccumulator |
基于函数的长整型原子累加 / 复合操作 | 复杂统计(如最大值、最小值、自定义聚合) | 支持自定义 LongBinaryOperator,高并发下性能更优 |
DoubleAccumulator |
基于函数的双精度浮点型原子复合操作 | 浮点型数据统计(如平均响应时间) | 同上(支持 DoubleBinaryOperator) |
LongAdder |
长整型原子累加(AtomicLong 的高性能替代) |
高并发计数器(如 QPS 统计) | 内部分桶存储,减少 CAS 冲突,高并发下性能远超 AtomicLong |
DoubleAdder |
双精度浮点型原子累加 | 浮点型计数器(如流量统计) | 同上 |
示例:LongAdder 高并发计数器(替代 AtomicLong)
java
运行
java
import java.util.concurrent.atomic.LongAdder;
// 高并发计数器:LongAdder 性能优于 AtomicLong(分桶减少冲突)
public class HighConcurrencyCounter {
private final LongAdder adder = new LongAdder();
// 原子自增(内部分桶,高并发下冲突更少)
public void increment() {
adder.increment();
}
// 原子累加
public void add(long delta) {
adder.add(delta);
}
// 获取总数(合并所有分桶的计数)
public long getCount() {
return adder.sum();
}
public static void main(String[] args) throws InterruptedException {
HighConcurrencyCounter counter = new HighConcurrencyCounter();
// 1000 个线程,每个线程自增 1000 次
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
}).start();
}
Thread.sleep(2000);
System.out.println("总计数:" + counter.getCount()); // 输出 1000000(准确)
}
}
示例:LongAccumulator 计算最大值
java
运行
java
import java.util.concurrent.atomic.LongAccumulator;
// 用 LongAccumulator 原子计算最大值
public class MaxValueCalculator {
public static void main(String[] args) {
// 自定义函数:取两个数的最大值(LongBinaryOperator)
LongAccumulator maxAccumulator = new LongAccumulator(
(x, y) -> Math.max(x, y), // 累加器函数
Long.MIN_VALUE // 初始值
);
// 多线程并发更新最大值
long[] values = {10, 20, 5, 30, 15};
for (long val : values) {
new Thread(() -> maxAccumulator.accumulate(val)).start();
}
// 等待所有线程执行完成
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("最大值:" + maxAccumulator.get()); // 输出 30
}
}
注意事项:
LongAdder/DoubleAdder的sum()方法是 "最终一致性"(合并分桶时可能有短暂不一致),适合 "最终统计" 场景(如 QPS 统计),不适合 "实时精确读取"(如事务计数)。- 复合操作原子类仅支持 "累加 / 聚合" 类逻辑,复杂复合操作(如 "先判断再更新")仍需结合锁或其他工具。
六、常用原子类选择总结
| 场景类型 | 推荐原子类 | 替代方案 |
|---|---|---|
单变量 int/long 计数 |
AtomicInteger/AtomicLong |
高并发用 LongAdder/DoubleAdder |
| 布尔状态标记 | AtomicBoolean |
AtomicReference<Boolean> |
| 原子更新对象引用 | AtomicReference |
需避免 ABA 用 AtomicStampedReference |
| 第三方类字段原子更新 | AtomicIntegerFieldUpdater 等 |
无法修改字段时的唯一选择 |
| 数组元素原子操作 | AtomicIntegerArray 等 |
手动加锁(性能差) |
| 复杂聚合操作(最大值 / 自定义) | LongAccumulator/DoubleAccumulator |
锁 + 普通变量(代码繁琐) |
| 高并发计数器(QPS / 流量) | LongAdder |
AtomicLong(低并发可用) |
核心原则:
- 简单计数 / 状态切换:优先用
AtomicInteger/AtomicBoolean(API 简洁,无学习成本)。 - 高并发计数:用
LongAdder(分桶减少冲突,性能最优)。 - 引用原子替换:用
AtomicReference(普通场景)或AtomicStampedReference(ABA 问题)。 - 第三方类字段更新:用字段更新器原子类(无需修改源码)。
- 复杂聚合逻辑:用
LongAccumulator/DoubleAccumulator(自定义函数,灵活高效)。