什么是 CAS?
CAS(Compare And Swap,比较并交换)是一种原子操作,就像是一个"智能开关"。
生活中的比喻
想象一个厕所门上的"有人/无人"指示牌:
传统方式(有问题):
1. 你看了一眼牌子:"无人"
2. 你准备去改牌子
3. 这时另一个人也看到"无人",也准备改牌子
4. 你们都改了牌子 → 两个人都进去了!❌
CAS 方式(安全):
1. 你看了一眼牌子:"无人"
2. 你对牌子说:"如果现在是'无人',就帮我改成'有人'"
3. 牌子会检查:如果确实还是"无人",就改成"有人",返回成功 ✅
4. 如果已经被别人改成了"有人",就返回失败 ❌
Java 中的 AtomicBoolean
AtomicBoolean 就是一个线程安全的布尔值,它提供了 CAS 操作。
核心方法:compareAndSet
AtomicBoolean flag = new AtomicBoolean(false);
// 尝试将 false 改成 true
boolean success = flag.compareAndSet(false, true);
if (success) {
System.out.println("成功获取执行权!");
} else {
System.out.println("已经有任务在运行了,本次跳过");
}
compareAndSet 的工作原理
public boolean compareAndSet(boolean expect, boolean update) {
// 伪代码逻辑:
// 1. 读取当前值
// 2. 如果当前值 == expect(期望值)
// 3. 则将当前值设置为 update(新值),返回 true
// 4. 否则返回 false
// 整个过程是原子的,不会被其他线程打断
}
实际应用场景
场景1:防止重复执行
public class TaskExecutor {
private final AtomicBoolean isRunning = new AtomicBoolean(false);
public void execute() {
// 尝试获取执行权
if (!isRunning.compareAndSet(false, true)) {
System.out.println("任务正在执行中,跳过本次请求");
return;
}
try {
// 执行业务逻辑
doWork();
} finally {
// 释放执行权
isRunning.set(false);
}
}
}
执行流程:
时刻1: 线程A调用 execute()
compareAndSet(false, true) → 成功,isRunning = true
开始执行任务...
时刻2: 线程B调用 execute()
compareAndSet(false, true) → 失败(当前是true)
直接返回,不执行任务
时刻3: 线程A执行完毕
finally 块中 isRunning.set(false)
时刻4: 线程C调用 execute()
compareAndSet(false, true) → 成功,isRunning = true
开始执行任务...
场景2:单次初始化
public class Singleton {
private static final AtomicBoolean initialized = new AtomicBoolean(false);
public void init() {
// 只有第一个调用的线程能成功
if (!initialized.compareAndSet(false, true)) {
return; // 已经初始化过了
}
// 执行初始化逻辑
doInit();
}
}
CAS vs synchronized
| 特性 | CAS (AtomicBoolean) | synchronized |
|---|---|---|
| 实现方式 | 硬件级别的原子指令 | JVM 层面的锁机制 |
| 性能 | 无锁竞争时更快 | 有锁的开销 |
| 阻塞 | 非阻塞,立即返回 | 可能阻塞等待 |
| 适用场景 | 简单的状态切换 | 复杂的同步逻辑 |
| 代码复杂度 | 简单 | 相对复杂 |
优缺点分析
优点 ✅
- 轻量级:不需要创建锁对象
- 非阻塞:不会让线程进入 WAITING 状态
- 简洁:一行代码完成检查和设置
- 高效:在低竞争场景下性能更好
缺点 ❌
- 需要手动管理状态:必须在 finally 中重置标志
- 容易忘记释放:如果忘记
set(false),后续所有请求都会被拒绝 - 不支持重入:同一个线程不能多次获取
- ABA 问题:虽然 Boolean 类型不存在这个问题
完整示例
import java.util.concurrent.atomic.AtomicBoolean;
public class SingleTaskRunner {
private final AtomicBoolean isRunning = new AtomicBoolean(false);
/**
* 执行任务,如果已有任务在执行则跳过
*/
public void run(Runnable task) {
// CAS 尝试获取执行权
if (!isRunning.compareAndSet(false, true)) {
System.out.println("⚠️ 任务正在执行中,本次请求被跳过");
return;
}
System.out.println("✅ 获取执行权,开始执行任务");
try {
task.run();
System.out.println("✅ 任务执行成功");
} catch (Exception e) {
System.err.println("❌ 任务执行异常: " + e.getMessage());
} finally {
// ⚠️ 重要:必须释放执行权
isRunning.set(false);
System.out.println("🔓 释放执行权");
}
}
// 测试
public static void main(String[] args) throws InterruptedException {
SingleTaskRunner runner = new SingleTaskRunner();
// 第一次调用:成功执行
runner.run(() -> {
System.out.println("任务1 执行中...");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
System.out.println("任务1 执行完毕");
});
// 立即第二次调用:会被跳过
runner.run(() -> {
System.out.println("任务2 执行中...");
});
// 等待2秒后第三次调用:可以执行
Thread.sleep(2500);
runner.run(() -> {
System.out.println("任务3 执行中...");
});
}
}
输出:
✅ 获取执行权,开始执行任务
任务1 执行中...
⚠️ 任务正在执行中,本次请求被跳过
任务1 执行完毕
🔓 释放执行权
✅ 获取执行权,开始执行任务
任务3 执行中...
🔓 释放执行权
总结
- CAS 是一种"乐观"的原子操作:先假设能成功,如果不成功就放弃
- AtomicBoolean 提供了线程安全的布尔值操作
- compareAndSet 是核心方法:如果当前值符合预期,就更新并返回 true
- 适合用于简单的状态标记和防重复执行场景
- 记得在
finally块中释放状态,避免死锁
2761

被折叠的 条评论
为什么被折叠?



