引言
在高并发编程领域,线程安全是永恒的核心议题。传统的悲观锁机制(如 synchronized、ReentrantLock)通过阻塞线程实现资源互斥,虽能保证安全性,但会带来上下文切换、线程唤醒等性能开销。而 CAS(Compare-And-Swap,比较并交换)作为乐观锁的核心实现,以无锁方式实现原子操作,成为高并发场景下提升性能的关键技术。
CAS 是一种无锁并发控制技术,基于 CPU 原子指令实现比较并交换。其核心包含内存位置、预期原值和目标新值,默认假设无冲突,失败则重试。主要问题包括 ABA 问题(可通过版本号解决)、自旋开销(高冲突下性能下降)及单变量限制。Java 原子类(如 AtomicInteger、LongAdder)封装了 Unsafe 操作,提供基本类型、数组、引用及字段更新支持。相比悲观锁,CAS 在低竞争下性能更优,适合计数器及无锁数据结构;高竞争时 LongAdder 通过分段优化优于 AtomicLong。

在高并发编程领域,线程安全是永恒的核心议题。传统的悲观锁机制(如 synchronized、ReentrantLock)通过阻塞线程实现资源互斥,虽能保证安全性,但会带来上下文切换、线程唤醒等性能开销。而 CAS(Compare-And-Swap,比较并交换)作为乐观锁的核心实现,以无锁方式实现原子操作,成为高并发场景下提升性能的关键技术。
CAS 的操作流程可概括为:读取内存位置 V 的当前值,若该值与预期原值 A 一致,则将 V 更新为新值 B 并返回成功;若不一致,则说明变量已被其他线程修改,放弃本次更新并返回失败。
// 伪代码表示 CAS 操作
boolean compareAndSwap(MemoryAddress V, ExpectedValue A, NewValue B) {
if (V == A) {
V = B;
return true;
}
return false;
}
其核心思想是'乐观假设无冲突'——默认不会有其他线程同时修改变量,仅在冲突发生时通过重试(而非阻塞)规避问题,从而避免了锁机制的性能损耗。
CAS 的原子性无法通过纯软件实现,必须依赖硬件层面的原子指令支持,整个依赖链路分为三层。
cmpxchg(Compare and Exchange)指令,该指令可在一条指令内完成'比较 - 交换'的原子操作ldrex(加载并标记)和 strex(存储并检查标记)指令对实现,标记期间其他线程无法修改目标内存地址,确保操作原子性Java 并未直接暴露 CPU 指令,而是通过 sun.misc.Unsafe 类提供的 native 方法封装底层操作。Unsafe 类是 JVM 内部使用的'危险类',允许直接操作内存地址和调用底层指令,其提供的 CAS 相关方法是原子类的核心依赖:
public final class Unsafe {
// 比较并交换 int 值
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
// 比较并交换 long 值
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
// 比较并交换 Object 值
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
}
这些方法通过 native 关键字调用 C++ 内联汇编代码,最终触发对应 CPU 架构的原子指令。需要注意的是,Unsafe 类不建议普通开发者直接使用,其操作无安全校验,可能引发内存越界等严重问题。
volatile 修饰
ABA 问题是 CAS 最经典的逻辑漏洞:变量从 A 被修改为 B,随后又被改回 A,CAS 操作会认为'值未变化'而成功执行,但中间的状态变更可能导致依赖变量状态的逻辑出错。
A→B,两个线程同时执行'删除头节点'操作
B;随后新增节点 A,链表变为 A→B(新 A 节点)解决方案:不仅校验变量值,还校验其'版本',确保值的变化轨迹可追溯。Java 中提供两类原子类实现该方案。
// 初始化:值为"A",版本号为 1
AtomicStampedReference<String> asr = new AtomicStampedReference<>("A", 1);
// 获取当前值和版本号
int oldStamp = asr.getStamp();
String oldValue = asr.getReference(); // "A"
// 仅当'值为 A 且版本号为 1'时,更新为"B"并将版本号设为 2
boolean success = asr.compareAndSet(oldValue, "B", oldStamp, oldStamp + 1);
CAS 失败时会通过'自旋'(循环重试 CAS 操作),若并发冲突剧烈,大量线程会持续自旋,占用 CPU 资源,导致性能下降。
解决方案:
自旋锁:线程拿不到锁就循环重试,无上下文切换开销,但消耗 CPU。适合短耗时操作 阻塞锁:线程拿不到锁就休眠,不消耗 CPU,但有巨大的上下文切换开销。适合长耗时操作
CAS 仅能对单个变量实现原子操作,无法直接保证多个变量组合操作的原子性(如'更新变量 a 同时更新变量 b')。
解决方案:
Java 并发包(java.util.concurrent.atomic)提供了一系列原子类,基于 sun.misc.Unsafe 类封装了常用原子操作,无需开发者直接操作底层指令。根据操作目标类型,原子类可分为四大类。
AtomicInteger - 原子更新 int 类型
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
public static void main(String[] args) {
// 创建 AtomicInteger 实例,初始值为 0
AtomicInteger atomicInt = new AtomicInteger(0);
// 基本操作示例
System.out.println("初始值:" + atomicInt.get()); // 输出:0
// 设置新值
atomicInt.set(10);
System.out.println("设置后:" + atomicInt.get()); // 输出:10
// 原子递增
atomicInt.incrementAndGet(); // i++
System.out.println("incrementAndGet 后:" + atomicInt.get()); // 输出:11
atomicInt.getAndIncrement(); // ++i
System.out.println("getAndIncrement 后:" + atomicInt.get()); // 输出:12
// 原子递减
atomicInt.decrementAndGet(); // i--
System.out.println("decrementAndGet 后:" + atomicInt.get()); // 输出:11
atomicInt.getAndDecrement(); // --i
System.out.println("getAndDecrement 后:" + atomicInt.get()); // 输出:10
// 原子加法
atomicInt.addAndGet(5); // 加 5 并返回新值
System.out.println("addAndGet(5) 后:" + atomicInt.get()); // 输出:15
int oldValue = atomicInt.getAndAdd(3); // 加 3 但返回旧值
System.out.println("getAndAdd(3) - 旧值:" + oldValue + ", 当前值:" + atomicInt.get()); // 输出:旧值:15, 当前值:18
// 原子更新(CAS 操作)
boolean success = atomicInt.compareAndSet(18, 20); // 如果当前值是 18,则更新为 20
System.out.println("CAS 操作成功:" + success + ", 当前值:" + atomicInt.get()); // 输出:true, 20
// 获取并更新
int newValue = atomicInt.updateAndGet(x -> x * 2); // 对当前值进行运算并返回新值
System.out.println("updateAndGet(x->x*2) 结果:" + newValue); // 输出:40
}
}
核心源码解析(以 AtomicInteger 为例)
public class AtomicInteger {
// 获取 Unsafe 实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// value 字段的内存偏移量(用于直接定位内存地址)
private static final long valueOffset;
// 共享变量,volatile 保证可见性
private volatile int value;
static {
try {
// 计算 value 字段在类中的内存偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
// CAS 核心方法:预期值与当前值一致则更新
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 原子自增(返回旧值)
public final int getAndIncrement() {
// 自旋 CAS:失败则重新读取并重试
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 原子自增(返回新值)
public final int incrementAndGet() {
return getAndAddInt(1) + 1;
}
}
其中,Unsafe.getAndAddInt 方法底层通过 do-while 循环实现自旋
public final class Unsafe {
// 以 volatile 语义读取对象 o 中偏移量为 offset 的 int 类型字段的值,并返回这个 int 值
public native int getIntVolatile(Object o, long offset);
// int 类型原子操作比较并交换
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
// 以 volatile 方式读取当前值(保证可见性)
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
// 失败则重试
return v;
}
}
compareAndSwapInt 工作原理(伪代码)
boolean compareAndSwapInt(Object o, long offset, int expected, int x) {
// 1. 计算字段的实际内存地址
int* address = (int*)((char*)o + offset);
// 2. 读取当前值
int current = *address;
// 3. 比较当前值与期望值
if (current == expected) {
// 4. 如果相等,则写入新值(原子操作)
*address = x;
return true; // 成功
} else {
return false; // 失败
}
}
⚠️ 注意:上面的伪代码不是原子的!真正的 compareAndSwapInt 是由 CPU 在硬件层面保证整个操作(比较 + 交换)是原子的,不会被线程调度打断
// 初始化 int 数组,所有元素原子更新
AtomicIntegerArray array = new AtomicIntegerArray(new int[]{1, 2, 3});
// 原子更新索引 0 的元素为 5(仅当当前值为 1 时)
array.compareAndSet(0, 1, 5);
// 原子自增索引 1 的元素
array.incrementAndGet(1);
System.out.println(array.get(0)); // 5
System.out.println(array.get(1)); // 3
针对对象引用的原子操作,除了前文解决 ABA 问题的 AtomicStampedReference(比较引用值 + 版本戳),还包括基础的 AtomicReference(比较引用值)和 AtomicMarkableReference(比较引用值 + 标记位),用于原子更新对象引用本身。
class Data {
private String content;
// 构造器、getter、setter省略
}
public class AtomicReferenceDemo {
private static final AtomicReference<Data> cacheRef = new AtomicReference<>(new Data("初始缓存"));
// 原子更新缓存
public static void updateCache(Data newData) {
cacheRef.compareAndSet(cacheRef.get(), newData);
}
public static void main(String[] args) {
Data newCache = new Data("更新后缓存");
updateCache(newCache);
System.out.println(cacheRef.get().getContent()); // 更新后缓存
}
}
// 初始化:值为"A",标记为 false(未修改)
AtomicMarkableReference<String> amr = new AtomicMarkableReference<>("A", false);
boolean[] markHolder = new boolean[1]; // 用于接收当前标记
String oldValue = amr.get(markHolder); // 获取值,markHolder[0] 为 false
// 仅当'值为 A 且标记为 false'时,更新为"B"并将标记设为 true
boolean success = amr.compareAndSet(oldValue, "B", false, true);
public class BankAccount {
// 普通属性
private String name;
// 正确做法:private + volatile
private volatile int balance;
private static final AtomicIntegerFieldUpdater<BankAccount> updater =
AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "balance");
// 修改 balance 值
public void deposit(int amount) {
// 只能通过原子操作更新
updater.addAndGet(this, amount);
}
// 自增 balance 值
public int increment() {
updater.incrementAndGet(this);
}
// 提供安全的读取方法
public int getBalance() {
return updater.get(this);
}
}
在高并发计数场景下,AtomicLong 的自旋 CAS 会因冲突剧烈导致性能下降。JDK 1.8 引入 LongAdder 和 DoubleAdder,通过'分段锁'思想优化性能。
核心原理
实战对比:LongAdder 与 AtomicLong 性能
public class LongAdderVsAtomicLong {
private static final AtomicLong atomicLong = new AtomicLong();
private static final LongAdder longAdder = new LongAdder();
private static final int THREAD_COUNT = 20;
private static final int LOOP_COUNT = 1000000;
public static void main(String[] args) throws InterruptedException {
// 测试 AtomicLong 性能
ExecutorService executor1 = Executors.newFixedThreadPool(THREAD_COUNT);
long start1 = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executor1.submit(() -> {
for (int j = 0; j < LOOP_COUNT; j++) {
atomicLong.incrementAndGet();
}
});
}
executor1.shutdown();
executor1.awaitTermination(1, TimeUnit.MINUTES);
long cost1 = System.currentTimeMillis() - start1;
System.out.println("AtomicLong 耗时:" + cost1 + "ms,结果:" + atomicLong.get());
// 测试 LongAdder 性能
ExecutorService executor2 = Executors.newFixedThreadPool(THREAD_COUNT);
long start2 = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
executor2.submit(() -> {
for (int j = 0; j < LOOP_COUNT; j++) {
longAdder.increment();
}
});
}
executor2.shutdown();
executor2.awaitTermination(1, TimeUnit.MINUTES);
long cost2 = System.currentTimeMillis() - start2;
System.out.println("LongAdder 耗时:" + cost2 + "ms,结果:" + longAdder.sum());
}
}
运行结果通常显示:高并发下 LongAdder 耗时仅为 AtomicLong 的 1/3~1/2,适合海量并发计数场景
| 对比维度 | CAS(乐观锁)/原子类 | 悲观锁 |
|---|---|---|
| 核心思想 | '先操作,后检测冲突'(假设冲突很少发生) | '先加锁,再操作'(假设冲突总发生) |
| 线程行为 | 无阻塞,冲突时自旋重试 | 竞争失败时线程挂起(上下文切换) |
| 性能开销 | 轻量级,没有锁竞争和唤醒开销 | 重量级,含上下文切换、锁调度开销 |
| 冲突处理 | 高冲突下自旋耗 CPU,性能下降 | 高冲突下稳定性更好,阻塞避免 CPU 空耗 |
| 功能范围 | 仅支持单个变量/字段的原子操作 | 支持任意代码块的原子性,功能更全面 |
| ABA 问题 | 存在,需额外机制解决 | 不存在,互斥访问避免中间状态变更 |

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online