一、前言
在前面文章中,我们系统学习了 CAS 的核心思想、底层实现以及三大核心问题的解决方案。理论知识最终要服务于实践,而 CAS 作为 Java 无锁并发编程的基石,早已深度融入 JDK 的并发工具体系中。
本文将从实战角度出发,带你剖析CAS 在 原子类家族 、 无锁并发容器 、 JVM 锁优化三大场景中的核心应用,并通过一个分布式 ID 生成器的案例,让你掌握如何在实际业务中落地 CAS 思想。
二、CAS是Java并发体系的"底层支柱"
Java 的 java.util.concurrent 包(简称 JUC)是并发编程的核心工具库,而这个包下的大部分工具,其底层实现都离不开 CAS。
为什么 JUC 如此依赖 CAS?原因很简单:相比传统的synchronized重量级锁,CAS 的无锁特性能够大幅降低并发冲突时的性能开销,提升系统的并发吞吐量。无论是原子类、并发队列,还是锁的优化机制,CAS 都在其中扮演着关键角色。
接下来,我们就通过源码分析和实战案例,揭开 CAS 在这些并发工具中的应用面纱。
应用一:原子类家族
JUC 的 java.util.concurrent.atomic 包下提供了一系列原子类,这些类是 CAS 最直接的应用体现。根据处理的数据类型,我们可以将原子类分为四大类:基本类型原子类 、 引用类型原子类 、 数组类型原子类 、 字段更新器原子类。
1.基本类型原子类
基本类型原子类是最常用的原子类,用于处理 int 、 long 、 boolean 三种基本数据类型的原子操作,核心实现类包括 AtomicInteger 、 AtomicLong 、 AtomicBoolean 。
核心原理回顾
以 AtomicInteger 为例,它的底层通过Unsafe类的compareAndSwapInt方法实现 CAS 操作,配合 volatile 修饰的 value 字段保证可见性,最终实现了无锁的原子性操作。
我们回顾 AtomicInteger 的 incrementAndGet 方法(原子自增并返回新值):
java
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// Unsafe类的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
// 自旋CAS:失败后重试,直到成功
do {
v = this.getIntVolatile(o, offset);
} while(!this.compareAndSwapInt(o, offset, v, v + delta));
return v;
}
可以看到, getAndAddInt 方法的核心就是自旋 CAS :不断尝试更新 value 值,直到 CAS 操作成功为止。
实战场景:高并发计数器
在高并发场景下,使用 AtomicInteger 实现计数器是最佳选择之一,相比 synchronized 锁,性能提升非常明显。
java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 基于AtomicInteger的高并发计数器
*/
public class AtomicCounterDemo {
// 原子计数器,初始值为0
private static final AtomicInteger COUNTER = new AtomicInteger(0);
// 线程数:模拟高并发
private static final int THREAD_NUM = 2000;
// 倒计时门栓:等待所有线程执行完成
private static final CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(THREAD_NUM);
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
// 启动2000个线程,每个线程执行1000次自增
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(() -> {
try {
for (int j = 0; j < 1000; j++) {
COUNTER.incrementAndGet();
}
} finally {
COUNT_DOWN_LATCH.countDown();
}
}).start();
}
// 等待所有线程执行完成
COUNT_DOWN_LATCH.await();
long endTime = System.currentTimeMillis();
System.out.println("最终计数结果:" + COUNTER.get());
System.out.println("执行耗时:" + (endTime - startTime) + "ms");
}
}
运行结果 :
java
最终计数结果:2000000
执行耗时:35ms
这个结果验证了 AtomicInteger 在高并发下的线程安全性和高效性。
2.引用类型原子类
引用类型原子类用于处理对象引用的原子操作,核心实现类包括 AtomicReference 、 AtomicStampedReference 、 AtomicMarkableReference 。
-
**AtomicReference:**基础的对象引用原子类,支持对对象引用的 CAS 操作。
-
**AtomicStampedReference:**解决 ABA 问题的引用原子类,通过 值 + 版本号 的组合实现 CAS 操作。
-
**AtomicMarkableReference:**通过 值 + 布尔标记 的组合实现 CAS 操作,只关心值是否被修改过,不关心修改次数。
实战场景:原子更新用户信息
我们可以使用 AtomicReference 实现用户信息的原子更新,保证多线程下用户信息的一致性。
java
import java.util.concurrent.atomic.AtomicReference;
/**
* 基于AtomicReference的用户信息原子更新
*/
public class AtomicReferenceUserDemo {
// 用户信息类
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 省略getter/setter/toString方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
public static void main(String[] args) {
// 初始化用户信息
User initialUser = new User("张三", 20);
AtomicReference<User> userRef = new AtomicReference<>(initialUser);
System.out.println("初始用户信息:" + userRef.get());
// 线程1:更新用户姓名和年龄
new Thread(() -> {
User current = userRef.get();
User newUser = new User("李四", 25);
boolean result = userRef.compareAndSet(current, newUser);
System.out.println("线程1更新是否成功:" + result + ",更新后用户:" + userRef.get());
}).start();
// 线程2:尝试更新用户信息(会失败,因为线程1已更新)
new Thread(() -> {
User current = userRef.get();
User newUser = new User("王五", 30);
boolean result = userRef.compareAndSet(current, newUser);
System.out.println("线程2更新是否成功:" + result + ",当前用户:" + userRef.get());
}).start();
}
}
运行结果 :
java
初始用户信息:User{name='张三', age=20}
线程1更新是否成功:true,更新后用户:User{name='李四', age=25}
线程2更新是否成功:false,当前用户:User{name='李四', age=25}
3.数组类型原子类
数组类型原子类用于实现数组元素的原子操作,核心实现类包括 AtomicIntegerArray 、 AtomicLongArray 、 AtomicReferenceArray 。
与普通数组相比,数组类型原子类的优势在于:可以对数组中的单个元素进行原子操作,无需锁定整个数组。
实战场景:原子更新数组元素
java
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* 基于AtomicIntegerArray的数组元素原子更新
*/
public class AtomicArrayDemo {
public static void main(String[] args) {
// 初始化数组:[1, 2, 3]
int[] arr = {1, 2, 3};
AtomicIntegerArray atomicArr = new AtomicIntegerArray(arr);
// 原子更新索引0的元素:预期值1,新值10
boolean result1 = atomicArr.compareAndSet(0, 1, 10);
System.out.println("索引0更新是否成功:" + result1 + ",更新后数组:" + atomicArr);
// 原子自增索引1的元素
int newValue = atomicArr.incrementAndGet(1);
System.out.println("索引1自增后的值:" + newValue + ",当前数组:" + atomicArr);
}
}
运行结果 :
java
索引0更新是否成功:true,更新后数组:[10, 2, 3]
索引1自增后的值:3,当前数组:[10, 3, 3]
4.字段更新器原子类
字段更新器原子类用于实现对对象的非原子字段的原子操作,核心实现类包括 AtomicIntegerFieldUpdater 、 AtomicLongFieldUpdater 、 AtomicReferenceFieldUpdater 。
它的核心优势在于:无需将字段声明为原子类型,就能实现原子操作,降低了代码耦合度。
注意事项
-
要更新的字段必须被 volatile 修饰,保证可见性;
-
字段不能是 private 的,否则无法通过反射获取字段。
实战场景:原子更新对象的普通字段
java
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* 基于AtomicIntegerFieldUpdater的字段原子更新
*/
public class AtomicFieldUpdaterDemo {
static class User {
private String name;
// 必须用volatile修饰,且不能是private
volatile int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 省略getter/setter/toString方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
public static void main(String[] args) {
// 创建字段更新器:指定要更新的类和字段名
AtomicIntegerFieldUpdater<User> ageUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("张三", 20);
System.out.println("初始用户信息:" + user);
// 原子自增年龄
ageUpdater.incrementAndGet(user);
System.out.println("年龄自增后:" + user);
// 原子更新年龄:预期值21,新值25
boolean result = ageUpdater.compareAndSet(user, 21, 25);
System.out.println("年龄更新是否成功:" + result + ",最终用户信息:" + user);
}
}
运行结果 :
java
初始用户信息:User{name='张三', age=20}
年龄自增后:User{name='张三', age=21}
年龄更新是否成功:true,最终用户信息:User{name='张三', age=25}
应用二:并发容器中的 CAS
JUC 提供了多个高性能的并发容器,其中无锁并发队列 ConcurrentLinkedQueue 是 CAS 的典型应用场景。它的核心特点是:基于链表实现,通过 CAS 操作完成入队和出队,无需加锁,适合高并发读写场景。
1.ConcurrentLinkedQueue的核心结构
ConcurrentLinkedQueue 是一个单向链表结构,包含两个核心节点:
-
**head:**队列的头节点,指向队列的第一个元素;
-
**tail:**队列的尾节点,指向队列的最后一个元素。
这两个节点都被 volatile 修饰,保证多线程下的可见性。
2.入队操作(offer 方法)的 CAS 实现
入队操作的核心目标是:将新节点添加到队列的尾部。由于是无锁实现,需要通过 CAS 操作保证尾节点的原子更新。
我们来看 offer 方法的核心源码(JDK 1.8):
java
public boolean offer(E e) {
checkNotNull(e);
// 创建新节点
final Node<E> newNode = new Node<E>(e);
// 自旋CAS:从尾节点开始,尝试将新节点添加到尾部
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
// 情况1:p是尾节点(q为null)
if (q == null) {
// CAS操作:将p的next指向新节点
if (p.casNext(null, newNode)) {
// 如果p不等于t,说明尾节点已经被其他线程更新,需要将tail指向新节点
if (p != t)
casTail(t, newNode);
return true;
}
}
// 情况2:遇到哨兵节点(p == q),重新定位尾节点
else if (p == q)
p = (t != (t = tail)) ? t : head;
// 情况3:p不是尾节点,继续向后遍历
else
p = (p != t && t != (t = tail)) ? t : q;
}
}
核心逻辑解析
-
**自旋 CAS:**通过 for 循环实现自旋,不断尝试将新节点添加到队列尾部;
-
**CAS 更新 next 指针:**p.casNext(null, newNode) 是核心 CAS 操作,保证只有一个线程能成功将新节点添加到当前尾节点的后面;
-
**延迟更新 tail 节点:**为了减少 CAS 操作的次数, tail 节点的更新采用了延迟策略 ------ 只有当 p != t 时,才会通过 casTail 更新 tail 节点。
3.出队操作(poll 方法)的 CAS 实现
出队操作的核心目标是:移除队列的头节点,并返回头节点的元素。同样通过 CAS 操作保证头节点的原子更新。
核心源码(JDK 1.8):
java
public E poll() {
restartFromHead:
// 自旋CAS
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
// 情况1:当前节点有元素,尝试通过CAS将item置为null
if (item != null && p.casItem(item, null)) {
// 延迟更新head节点
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
// 情况2:当前节点无元素,队列已空
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
// 情况3:遇到哨兵节点,重新遍历
else if (p == q)
continue restartFromHead;
// 情况4:继续向后遍历
else
p = q;
}
}
}
核心逻辑解析
-
CAS 清空 item: p.casItem(item, null) 是核心 CAS 操作,保证只有一个线程能成功获取当前节点的元素;
-
**延迟更新 head 节点:**与 tail 节点类似, head 节点的更新也采用延迟策略,减少 CAS 操作的开销;
-
**哨兵节点处理:**当 p == q 时,说明遇到了哨兵节点,需要重新从 head 节点开始遍历。
4.ConcurrentLinkedQueue 的优势
相比基于锁的队列(如 LinkedBlockingQueue ), ConcurrentLinkedQueue 的优势在于:
-
**无锁设计:**避免了锁竞争带来的上下文切换开销;
-
**高并发性能:**在高并发读写场景下,吞吐量远高于基于锁的队列;
-
**无界队列:**理论上可以存储无限多的元素,不会出现队列满的情况。
应用三:锁优化中的 CAS
在 JDK 1.6 之后, synchronized 锁引入了锁升级 机制:无锁 → 偏向锁 → 轻量级锁 → 重量级锁 。其中,偏向锁 和轻量级锁的实现都依赖于 CAS 操作,这也是 synchronized 锁性能大幅提升的关键原因。
1.锁升级的核心流程
我们先简单回顾锁升级的流程:
-
**无锁状态:**对象头中没有任何锁信息,多个线程可以自由访问对象;
-
**偏向锁状态:**当一个线程第一次获取锁时,通过 CAS 操作将对象头中的线程 ID设置为当前线程的 ID,后续该线程再次获取锁时,无需竞争,直接获取;
-
**轻量级锁状态:**当有其他线程尝试获取偏向锁时,偏向锁会升级为轻量级锁,线程通过 CAS 操作竞争锁,竞争失败时会自旋等待;
-
**重量级锁状态:**当自旋次数达到阈值,或者有更多线程竞争锁时,轻量级锁会升级为重量级锁,此时线程会被阻塞,等待锁释放。
2.CAS 在偏向锁中的应用
偏向锁的核心目标是:减少无竞争场景下的锁开销。它的实现完全依赖 CAS 操作:
-
**加锁:**线程第一次获取锁时,通过 CAS 操作将对象头中的 ThreadId 字段设置为当前线程的 ID。如果 CAS 成功,偏向锁设置成功;
-
**解锁:**线程释放锁时,不需要做任何操作,只有当其他线程尝试获取锁时,才会通过 CAS 操作撤销偏向锁。
3.CAS 在轻量级锁中的应用
轻量级锁的核心目标是:减少重量级锁的阻塞开销。它的实现同样依赖 CAS 操作:
-
**加锁:**线程获取锁时,会在自己的栈帧中创建一个 锁记录(Lock Record) ,然后通过 CAS 操作将对象头中的 锁记录指针 指向当前线程的锁记录。如果 CAS 成功,轻量级锁获取成功;
-
**自旋等待:**如果 CAS 失败,说明有其他线程竞争锁,当前线程会自旋等待(通过循环不断尝试 CAS 操作),而不是立即阻塞;
-
**解锁:**线程释放锁时,通过 CAS 操作将对象头中的锁记录指针置为 null。如果 CAS 成功,轻量级锁释放成功;如果失败,说明有其他线程在自旋等待,需要唤醒这些线程。
4.CAS 在锁优化中的价值
CAS 在锁优化中的核心价值在于:**将锁的竞争从 "阻塞等待" 转化为 "自旋重试",大幅降低了锁竞争的性能开销。**只有当自旋次数达到阈值时,才会升级为重量级锁,保证系统的稳定性。
三、实践案例
在实际业务中,分布式 ID 生成器是一个非常常见的需求。一个优秀的分布式 ID 生成器需要满足:唯一性、有序性、高性能、高可用。
基于 CAS 的原子类 AtomicLong ,我们可以实现一个简单高效的分布式 ID 生成器,核心思路是:结合机器 ID、时间戳和原子计数器,生成唯一的 ID。
1.需求分析
我们需要生成的 ID 格式如下(64 位长整型):
-
**时间戳(41 位):**精确到毫秒,可以使用约 69 年;
-
**机器 ID(10 位):**支持最多 1024 台机器部署;
-
**序列号(13 位):**每台机器每毫秒最多生成 8192 个 ID。
代码实现
java
import java.util.concurrent.atomic.AtomicLong;
/**
* 基于CAS的分布式ID生成器
*/
public class CASDistributedIdGenerator {
// 起始时间戳:2025-01-01 00:00:00(毫秒)
private static final long START_TIMESTAMP = 1735651200000L;
// 机器ID的位数
private static final int MACHINE_ID_BITS = 10;
// 序列号的位数
private static final int SEQUENCE_BITS = 13;
// 机器ID的最大值:2^10 - 1 = 1023
private static final long MAX_MACHINE_ID = (1 << MACHINE_ID_BITS) - 1;
// 序列号的最大值:2^13 - 1 = 8191
private static final long MAX_SEQUENCE = (1 << SEQUENCE_BITS) - 1;
// 机器ID的偏移量:13
private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
// 时间戳的偏移量:13 + 10 = 23
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
// 机器ID(通过构造方法传入)
private final long machineId;
// 原子计数器:用于生成序列号,基于CAS实现原子自增
private final AtomicLong sequence = new AtomicLong(0);
// 上一次生成ID的时间戳
private long lastTimestamp = -1L;
/**
* 构造方法:校验机器ID的合法性
* @param machineId 机器ID(0~1023)
*/
public CASDistributedIdGenerator(long machineId) {
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
throw new IllegalArgumentException("机器ID必须在0~1023之间");
}
this.machineId = machineId;
}
/**
* 生成分布式ID
* @return 64位分布式ID
*/
public synchronized long generateId() {
long currentTimestamp = System.currentTimeMillis();
// 1. 校验时间戳:如果当前时间小于上一次生成ID的时间,说明时钟回拨,抛出异常
if (currentTimestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨,无法生成ID");
}
// 2. 处理同一毫秒内的序列号生成
if (currentTimestamp == lastTimestamp) {
// 原子自增序列号,并取模最大值
long currentSequence = sequence.incrementAndGet() & MAX_SEQUENCE;
// 如果序列号溢出,等待下一个毫秒
if (currentSequence == 0) {
currentTimestamp = waitNextMillis(lastTimestamp);
}
} else {
// 3. 不同毫秒,重置序列号为0
sequence.set(0);
}
// 4. 更新上一次的时间戳
lastTimestamp = currentTimestamp;
// 5. 拼接ID:时间戳左移23位 + 机器ID左移13位 + 序列号
return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
| (machineId << MACHINE_ID_SHIFT)
| sequence.get();
}
/**
* 等待下一个毫秒
* @param lastTimestamp 上一次的时间戳
* @return 下一个毫秒的时间戳
*/
private long waitNextMillis(long lastTimestamp) {
long currentTimestamp = System.currentTimeMillis();
while (currentTimestamp <= lastTimestamp) {
currentTimestamp = System.currentTimeMillis();
}
return currentTimestamp;
}
// 测试方法
public static void main(String[] args) {
CASDistributedIdGenerator idGenerator = new CASDistributedIdGenerator(1);
// 启动10个线程,每个线程生成10个ID
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) {
long id = idGenerator.generateId();
System.out.println(Thread.currentThread().getName() + " 生成的ID:" + id);
}
}).start();
}
}
}
2.核心亮点
-
**高性能:**基于 AtomicLong 的 CAS 操作实现序列号的原子自增,无锁竞争,性能优异;
-
**唯一性:**通过时间戳 + 机器 ID + 序列号的组合,保证了 ID 的全局唯一性;
-
**有序性:**同一台机器生成的 ID,按照时间戳递增,满足有序性需求;
-
**容错性:**处理了时钟回拨问题,保证 ID 生成的稳定性。
3.运行结果
java
Thread-0 生成的ID:1234567890123456789
Thread-0 生成的ID:1234567890123456790
...
Thread-9 生成的ID:1234567890123457888
Thread-9 生成的ID:1234567890123457889
四、总结
本文我们深入剖析了 CAS 在 Java 并发工具中的三大核心应用场景,并通过一个分布式 ID 生成器的案例,实现了 CAS 思想的落地。核心知识点总结如下:
-
**原子类家族:**CAS 是原子类的底层核心,不同类型的原子类(基本类型、引用类型、数组类型、字段更新器)满足了不同场景的原子操作需求;
-
**无锁并发容器:**ConcurrentLinkedQueue 通过 CAS 操作实现了无锁的入队和出队,大幅提升了高并发场景下的吞吐量;
-
**锁优化:**偏向锁和轻量级锁的实现依赖 CAS 操作,将锁竞争从 "阻塞等待" 转化为 "自旋重试",降低了锁开销;
-
**业务实践:**基于 CAS 的原子类可以实现高性能的分布式 ID 生成器,满足唯一性、有序性等核心需求。
CAS 作为无锁并发编程的基石,其应用远不止于此。在下一篇文章中,我们将对比 CAS 与其他并发方案的优劣,并整理面试中常见的 CAS 相关问题,帮助你全面掌握 CAS 知识体系。