👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》
什么是CAS?
说到原子类,首先就要说到CAS:
CAS(Compare and Swap) 是一种无锁的原子操作,用于实现多线程环境下的安全数据更新。
CAS(Compare and Swap) 的本质是 "无锁更新" 。它的核心思想是:
- 先检查:在修改共享变量之前,先检查当前值是否符合预期。
- 再更新:如果符合预期,则更新为新值;否则放弃或重试。
- 原子性保证 :整个过程由 CPU 硬件指令(如
cmpxchg
)直接支持,确保不可中断。
Java 通过 java.util.concurrent.atomic
包中的原子类(如 AtomicInteger
、AtomicReference
等)提供 CAS 支持。

简单使用原子类
主播这里挑几个,主播觉得常见的,具体说说。
AtomicInteger
先从一个简单的案例开始,使用AtomicInteger
实现线程安全计数器:
java
public class AtomicIntegerTest {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
// 使用 CAS 安全递增
count.incrementAndGet();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
// 使用 CAS 安全递增
count.incrementAndGet();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final Count: " + count.get()); // 输出 2000
}
}
一个简单的案例,主播直接下面具体说说AtomicInteger的一些方法:
基本方法:
java
int get() // 返回当前值(线程安全获取)。
void set(int newValue) // 直接设置新值(非原子性,但保证可见性)。
void lazySet(int newValue) // 延迟设置新值(最终可见,但不保证其他线程立即看到)。
原子增减:
java
int incrementAndGet() // 先自增 +1,再返回新值(等价于 ++i)。
int getAndIncrement() // 先返回当前值,再自增 +1(等价于 i++)。
int decrementAndGet() // 先自减 -1,再返回新值(等价于 --i)。
int getAndDecrement() // 先返回当前值,再自减 -1(等价于 i--)。
原子更新:
java
int addAndGet(int delta) // 先增加 delta,再返回新值。
int getAndAdd(int delta) // 先返回当前值,再增加 delta。
boolean compareAndSet(int expect, int update) // CAS 操作:若当前值等于 expect,则更新为 update,返回是否成功。
int updateAndGet(IntUnaryOperator updateFunction) // 用函数式更新值,返回新值(如 x -> x * 2)。
int getAndUpdate(IntUnaryOperator updateFunction) // 用函数式更新值,返回旧值。
其他方法
java
int getAndSet(int newValue) // 设置新值并返回旧值。(原子操作的,请放心)
int intValue() // 继承自 Number,返回当前值的 int 形式(等价于 get())。
AtomicReference
在多线程环境中,如果多个线程同时修改一个共享的可变对象,可能会导致数据不一致。传统的解决方法是使用 synchronized
或 Lock
加锁,但锁会导致线程阻塞,降低并发性能。AtomicReference
使用无锁的 CAS(Compare-And-Swap)机制,通过硬件级别的原子指令直接操作内存,既保证线程安全,又避免了锁的开销。
( ̄▽ ̄)"让我们先从一个简单的案例来开始认识AtomicReference
吧!!
【案例】修改不可变对象
java
// 1. 定义不可变对象
class ImmutableConfig {
private final String serverUrl;
private final int timeout;
public ImmutableConfig(String serverUrl, int timeout) {
this.serverUrl = serverUrl;
this.timeout = timeout;
}
public String getServerUrl() { return serverUrl; }
public int getTimeout() { return timeout; }
}
使用AtomicReference
管理配置:
java
// 2. 使用 AtomicReference 管理配置
public class ConfigManager {
private final AtomicReference<ImmutableConfig> configRef;
public ConfigManager(String initialUrl, int initialTimeout) {
configRef = new AtomicReference<>(new ImmutableConfig(initialUrl, initialTimeout));
}
// 原子更新配置(创建新对象并替换引用)
public void updateConfig(String newUrl, int newTimeout) {
ImmutableConfig oldConfig;
ImmutableConfig newConfig;
do {
oldConfig = configRef.get(); // 获取当前配置
newConfig = new ImmutableConfig(newUrl, newTimeout); // 创建新配置
} while (!configRef.compareAndSet(oldConfig, newConfig)); // CAS 更新
}
// 获取当前配置(线程安全)
public ImmutableConfig getCurrentConfig() {
return configRef.get();
}
}
测试类
java
public static void main(String[] args) {
ConfigManager manager = new ConfigManager("http://default-server", 5000);
// 线程1:更新配置
new Thread(() -> {
manager.updateConfig("http://new-server-1", 8000);
System.out.println("Thread1 updated config: " + manager.getCurrentConfig());
}).start();
// 线程2:同时更新配置
new Thread(() -> {
manager.updateConfig("http://new-server-2", 10000);
System.out.println("Thread2 updated config: " + manager.getCurrentConfig());
}).start();
}
主播也是简单的收集了下,这个类的方法:
java
get() // 获取当前对象引用值(保证内存可见性)
set(V newValue) // 原子性设置新引用值(无返回值)
getAndSet(V newValue) // 原子性操作:返回旧值并设置新值
compareAndSet(V expect, V update) // (CAS 操作) 当当前值等于 expect 时,原子性更新为 update(返回是否成功)
lazySet(V newValue) // 延迟设置新值(不保证其他线程立刻看到更新,性能优化用)
updateAndGet(UnaryOperator<V> updateFunction) // 原子性更新引用并返回新值
getAndUpdate(UnaryOperator<V> updateFunction) // 原子性更新引用并返回旧值
AtomicStampedReference&AtomicMarkableReference
说到AtomicStampedReference,那就必须先说说ABA问题勒😁
ABA 问题是 无锁编程(如 CAS 操作) 中一个经典问题,具体表现为:
- 线程1 读取共享变量的值为
A
。 - 在 线程1 准备修改该值时,线程2 将值从
A
改为B
,随后又改回A
。 - 线程1 执行 CAS 操作时,发现当前值仍是
A
,误以为未被修改过,于是继续操作。
问题本质:值看似未变,但中间经历了其他修改,可能导致逻辑错误。
ABA 问题的根源在于 值被多次修改后还原,但中间过程未被感知 。解决方法是为每次修改附加一个 版本号(或时间戳) ,使得:
即使值相同,版本号不同,CAS 也会失败。
1、让我们看看AtomicStampedReference的解决
定义共享资源
java
static class Resource {
String data;
public Resource(String data) { this.data = data; }
}
主类
java
public static void main(String[] args) {
// 初始引用:resourceA,版本号 0
Resource resourceA = new Resource("A");
AtomicStampedReference<Resource> stampedRef =
new AtomicStampedReference<>(resourceA, 0);
// 线程1:尝试修改 ResourceA → B → A,并增加版本号
new Thread(() -> {
int[] stampHolder = new int[1];
Resource current = stampedRef.get(stampHolder); // 获取当前值和版本号
// 模拟 ABA 操作(A → B → A)
stampedRef.compareAndSet(current, new Resource("B"), stampHolder[0], stampHolder[0] + 1);
stampedRef.compareAndSet(stampedRef.getReference(), resourceA, stampedRef.getStamp(), stampedRef.getStamp() + 1);
}).start();
// 线程2:检查值是否被修改过(即使值还是A,版本号已变化)
new Thread(() -> {
try {
Thread.sleep(500); // 等待线程1完成ABA操作
} catch (InterruptedException e) {}
int[] stampHolder = new int[1];
Resource current = stampedRef.get(stampHolder);
boolean success = stampedRef.compareAndSet(
current,
new Resource("C"),
stampHolder[0], // 预期原版本号(此时已不是0)
stampHolder[0] + 1
);
System.out.println("更新是否成功? " + success); // 输出:false(因为版本号已变)
}).start();
}
主播也是整理了下,这个类的方法:
java
getReference() // 获取当前存储的引用对象(非原子性组合操作,需结合版本号使用)
getStamp() // 获取当前版本号(非原子性组合操作)
get(int[] stampHolder) // 原子性获取 引用值 + 版本号(通过数组传递版本号)
compareAndSet(V expectedRef, V newRef, int expectedStamp, int newStamp) // CAS 核心操作:当且仅当当前引用值等于 expectedRef 且 版本号等于 expectedStamp 时,更新引用和版本号
set(V newRef, int newStamp) // 直接设置新引用值和新版本号(非原子组合操作,慎用)
attemptStamp(V expectedRef, int newStamp) // 仅当当前引用等于 expectedRef 时,更新版本号(不改变引用)
2.再让我们看看AtomicMarkableReference的解决
AtomicMarkableReference 通过布尔标记降低 ABA 发生概率,但无法完全避免,实际使用中需结合场景评估风险。
java
// 初始值:reference = "A", mark = false
AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A", false);
// 更新时检查值和标记
boolean success = ref.compareAndSet(
"A", // 预期原值
"B", // 新值
false, // 预期原标记
true // 新标记
);
设计初衷 并非为彻底解决 ABA 问题,而是提供一种轻量级标记机制,适用于对 ABA 不敏感但需简单版本标识的场景。
AtomicIntegerArray
AtomicIntegerArray 用于在多线程环境下原子性地操作一个整数数组 。它提供了对数组中每个元素的原子性操作(如 get
、set
、compareAndSet
、incrementAndGet
等),确保多线程修改数组元素时的线程安全性。每个方法(如 get
、set
、addAndGet
)都是原子性的,无需额外同步。
让我们从一个简单的例子开始吧!!
java
public class AtomicIntegerArrayExample {
private static final int ARRAY_LENGTH = 5;
private static AtomicIntegerArray atomicArray = new AtomicIntegerArray(ARRAY_LENGTH);
public static void main(String[] args) throws InterruptedException {
// 创建两个线程,分别对数组的不同索引进行自增操作
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
atomicArray.incrementAndGet(0); // 原子性地将索引 0 的元素自增 1
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
atomicArray.incrementAndGet(0); // 两个线程操作同一个索引
}
});
thread1.start();
thread2.start();
// 等待线程执行完毕
thread1.join();
thread2.join();
// 输出结果:2000(无竞态条件)
System.out.println("Final value at index 0: " + atomicArray.get(0));
}
}
聪明的你一定又学会了吧!主播这里简单整理了下其他方法:
java
get(int i) // 获取索引 i 处的当前值(保证内存可见性)。
set(int i, int newValue) // 直接设置索引 i 处的值为 newValue(无原子性保证,但保证写入后对其他线程可见)。
lazySet(int i, int newValue) // 延迟设置值(性能优化,不保证其他线程立刻可见)。
compareAndSet(int i, int expect, int update) // CAS 操作:当索引 i 处的值等于 expect 时,原子性更新为 update,返回是否成功。
getAndSet(int i, int newValue) // 原子性获取旧值并设置新值。
// 复合原子操作
getAndUpdate(int i, IntUnaryOperator updateFunction) // 原子性应用函数到索引 i 处的值,返回旧值。
updateAndGet(int i, IntUnaryOperator updateFunction) // 原子性应用函数到索引 i 处的值,返回新值。
getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction) // 原子性将索引 i 处的值与 x 通过函数计算,返回旧值。
accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction) // 同上,但返回新值。
// 自增/自减快捷方法
getAndIncrement(int i) // 原子性自增(旧值 +1,返回旧值)。
getAndDecrement(int i) // 原子性自减(旧值 -1,返回旧值)。
getAndAdd(int i, int delta) // 原子性增加 delta,返回旧值。
incrementAndGet(int i) // 自增后返回新值(等价于 ++i)。
LongAdder
LongAdder
是 专门用于高并发场景下的累加操作 。它在多线程环境下性能优于 AtomicLong
,尤其是在高竞争(多线程频繁修改值)的场景中,因为它采用了分段锁(Cell 分段) 的策略减少线程竞争。
具体原理下一篇会说,这里我们先来看看差异
java
public class PerformanceComparison {
private static final int THREAD_COUNT = 1000; // 线程数
private static final int OPERATIONS = 100000; // 每个线程的操作次数
// 测试 LongAdder
private static void testLongAdder() throws InterruptedException {
LongAdder adder = new LongAdder();
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executor.submit(() -> {
for (int j = 0; j < OPERATIONS; j++) {
adder.increment(); // 无锁分段累加
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
long duration = System.currentTimeMillis() - start;
System.out.println("LongAdder 耗时: " + duration + "ms, 结果: " + adder.sum());
}
// 测试 AtomicLong
private static void testAtomicLong() throws InterruptedException {
AtomicLong atomicLong = new AtomicLong(0);
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executor.submit(() -> {
for (int j = 0; j < OPERATIONS; j++) {
atomicLong.incrementAndGet(); // 基于 CAS 的原子操作
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
long duration = System.currentTimeMillis() - start;
System.out.println("AtomicLong 耗时: " + duration + "ms, 结果: " + atomicLong.get());
}
public static void main(String[] args) throws InterruptedException {
testLongAdder(); // 先测试 LongAdder
testAtomicLong(); // 再测试 AtomicLong
}
}
输出结果
shell
LongAdder 耗时: 304ms, 结果: 100000000
AtomicLong 耗时: 1782ms, 结果: 100000000
主播这里就贴心的准备了其他方法:
java
add(long x) // 原子性增加指定值(可正可负),无返回值。
increment() // 原子性自增 1(等价于 add(1))。
decrement() // 原子性自减 1(等价于 add(-1))。
sum() // 返回当前总和(非原子快照,并发时可能不精确)。
reset() // 重置所有计数器为 0(非原子操作,需谨慎使用)。
sumThenReset() // 返回当前总和并重置计数器(类似"获取并清零"操作)。
和AtomicLong的对比
机制 | LongAdder | AtomicLong |
---|---|---|
存储方式 | 分散到多个 Cell 中 |
单一的 volatile long 变量 |
竞争处理 | 线程优先修改各自对应的 Cell ,减少冲突 |
所有线程竞争同一个变量的 CAS 操作 |
读取结果 | 调用 sum() 需要合并所有 Cell 的值 |
get() 直接返回当前值 |
适用场景 | 高并发写入,低频读取(如统计计数) | 低并发或需要实时读取值的场景 |
后话
( ̄▽ ̄)"怎么样?聪明的你是否对原子类的使用,拥有了更多的理解。