极限飞行大师手游
54.66 MB · 2025-10-30
好的,我们来详细、深入地解析一下 CAS(Compare and Swap)。
CAS 是现代并发编程的基石之一,它使得无锁(Lock-Free)和高性能的并发算法成为可能。
CAS 是一种原子操作。它用于在多线程环境下实现同步,而无需使用重量级的锁(如 synchronized 或 ReentrantLock)。
它的核心思想是:“我认为内存位置 V 的值应该是 A,如果是,那么把它更新为 B;否则,不要修改它,但要告诉我当前的实际值是什么。”
一个 CAS 操作包含三个操作数:
CAS 的语义可以用以下伪代码来表示:
boolean CAS(V, A, B) {
if (V == A) { // 只有在内存值等于预期原值时,才进行更新
V = B
return true // 表示更新成功
} else {
return false // 表示更新失败
}
}
关键在于,整个比较和交换的过程是一个单一的、不可中断的原子操作。CPU 提供了一条特殊的指令(在 x86 架构上是 CMPXCHG)来保证这一点。
当多个线程同时尝试对同一个内存位置进行 CAS 操作时,会发生什么?
V,假设值为 100。100 增加 1,所以它们都计算出新值 101。V 的值是否还是它之前读到的 100。100,所以 CAS 成功。V 被更新为 101。CAS(V, 100, 101) 返回 true。V 的值是否还是它之前读到的 100。V 已经被线程 1 改为 101 了。CAS(V, 100, 101) 返回 false。V 的当前值(现在是 101)。101 + 1 = 102)。CAS(V, 101, 102)。这个过程就像在说:“我先看看现在是多少,算好新值,然后尝试更新。如果有人在我之前改动了,那我就再试一次。”
在 Java 中,CAS 操作是通过 sun.misc.Unsafe 类中的本地(Native)方法实现的。但我们通常不会直接使用 Unsafe 类,而是使用 JDK 封装好的原子类。
最经典的例子就是 java.util.concurrent.atomic.AtomicInteger。
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
public static void main(String[] args) {
AtomicInteger atomicInt = new AtomicInteger(100);
// 模拟线程1
boolean success1 = atomicInt.compareAndSet(100, 101); // CAS 操作
System.out.println("Thread 1 CAS: " + success1 + ", value: " + atomicInt.get()); // true, 101
// 模拟线程2
boolean success2 = atomicInt.compareAndSet(100, 101); // CAS 操作,但预期值已是旧的100
System.out.println("Thread 2 CAS: " + success2 + ", value: " + atomicInt.get()); // false, 101
// 线程2重试
boolean success3 = atomicInt.compareAndSet(101, 102); // 使用新读到的值101作为预期值
System.out.println("Thread 2 Retry CAS: " + success3 + ", value: " + atomicInt.get()); // true, 102
}
}
输出:
Thread 1 CAS: true, value: 101
Thread 2 CAS: false, value: 101
Thread 2 Retry CAS: true, value: 102
compareAndSet 方法就是 Java 中 CAS 操作最直接的体现。
AtomicInteger,实现线程安全的递增递减。ConcurrentLinkedQueue(无锁队列)、ConcurrentHashMap(在 JDK 1.8 的部分实现中使用了 CAS)等高性能并发容器。高性能:
避免死锁:因为不使用锁,所以从根本上避免了死锁问题。
ABA 问题:
AtomicStampedReference 和 AtomicMarkableReference 来解决 ABA 问题。循环时间长开销大:
只能保证一个共享变量的原子操作:
AtomicReference 来保证这个对象引用的原子性。尽管 CAS 非常高效,但它也存在一些著名的缺陷:
AtomicStampedReference(维护一个版本号戳)和 AtomicMarkableReference(维护一个布尔标记)来解决这个问题。for 循环会不停地重试,会占用大量的 CPU 资源。AtomicReference 来保证引用类型对象的原子性,从而间接实现多个变量的原子更新。| 特性 | 描述 |
|---|---|
| 全称 | Compare And Swap(比较并交换) |
| 本质 | 一条 CPU 原子指令 |
| 核心思想 | 我认为 V 的值应该是 A,如果是,则更新为 B;否则,不更新并告知失败。 |
| 在 Java 中的体现 | AtomicXXX 类(如 AtomicInteger)、Unsafe 类 |
| 优势 | 高性能(无阻塞)、避免死锁 |
| 劣势 | ABA 问题、高竞争下 CPU 空耗、只能保证一个变量的原子性 |
核心要点:
CAS 是乐观锁的一种实现方式。它假设冲突很少发生,所以先大胆地去操作,如果发现冲突(CAS 失败),再重试。这与传统的悲观锁(如 synchronized)形成鲜明对比,悲观锁假设冲突经常发生,所以每次操作前都要先加锁,确保独占访问。
理解 CAS 是理解现代高性能并发库(如 java.util.concurrent 包)如何工作的关键。
CAS 是现代计算机科学中一个极其重要的概念,它是构建无锁并发数据结构、实现高效同步机制的基石。
1. 什么是 CAS?
CAS,全称 Compare and Swap,是一种原子操作。它用于在多线程环境下实现同步,而无需使用传统的锁(如 synchronized 或 ReentrantLock)。
它的核心思想是:“我认为值 V 应该是 A,如果是,那么把它更新为 B;否则,不要修改它,但要告诉我现在实际的值是什么。”
2. CAS 的操作语义
CAS 操作通常包含三个操作数:
其语义可以用以下伪代码来理解:
boolean CAS(V, A, B) {
if (V == A) { // 只有在当前值符合预期时,才进行更新
V = B;
return true; // 成功
} else {
return false; // 失败
}
}
关键在于,这个比较和交换的动作是一个不可分割的原子操作。 在现代CPU上,这通常是由一条特殊的硬件指令(如 x86 架构上的 CMPXCHG)来完成的,从而保证了在多个线程同时尝试 CAS 同一个内存位置时,不会出现数据竞争。
3. 一个生动的例子:乐观锁
想象一个多人协同编辑的文档(比如 Google Docs)。
synchronized,你获取了锁,其他线程被阻塞)。V == A)这个“保存时检查”的过程,就是 CAS 的精髓。它避免了长时间的阻塞,允许多个参与者同时工作,只在最后提交时解决冲突。
4. CAS 在 Java 中的实现
在 Java 中,CAS 操作并不是通过关键字直接暴露给开发者的,而是通过 sun.misc.Unsafe 类中的本地方法(compareAndSwapObject, compareAndSwapInt, compareAndSwapLong)来实现的。
然而,对于普通开发者,更常用的是 java.util.concurrent.atomic 包下的原子类,它们封装了这些底层的 CAS 操作。
以 AtomicInteger 为例:
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
public static void main(String[] args) {
AtomicInteger atomicInt = new AtomicInteger(0); // 初始值为 0
// 模拟一个典型的 CAS 操作:如果当前值是 0,就把它设置为 1
boolean success1 = atomicInt.compareAndSet(0, 1); // CAS(V=atomicInt, A=0, B=1)
System.out.println("第一次 CAS: " + success1 + ", 当前值: " + atomicInt.get()); // 输出: true, 1
// 再次尝试:当前值已经是 1 了,但我们仍预期它是 0
boolean success2 = atomicInt.compareAndSet(0, 2); // CAS(V=atomicInt, A=0, B=2)
System.out.println("第二次 CAS: " + success2 + ", 当前值: " + atomicInt.get()); // 输出: false, 1
}
}
另一个核心方法:getAndIncrement()
我们经常使用的 i++ 操作不是原子的。但 AtomicInteger 的 getAndIncrement() 是原子的,它的内部就是通过 CAS 实现的,通常被称为 自旋。
public final int getAndIncrement() {
// 这是一个典型的自旋循环 CAS 模式
for (;;) { // 循环,直到成功为止
int current = get(); // 获取当前值,作为预期原值 A
int next = current + 1; // 计算新值 B
if (compareAndSet(current, next)) { // 尝试 CAS
return current; // 如果成功,返回旧值
}
// 如果失败,循环重试
}
}
5. CAS 的优缺点
优点:
缺点:
ABA 问题:
AtomicStampedReference 和 AtomicMarkableReference 来解决 ABA 问题。它们不仅在比较值,还会比较一个版本号(Stamp)。循环时间长开销大:
for 循环会一直重试,会占用大量的 CPU 资源。如果线程数非常多,不如使用传统的锁效率高。只能保证一个共享变量的原子操作:
AtomicReference 来 CAS 这个对象。6. 总结
| 特性 | CAS(乐观锁) | 传统锁(悲观锁) |
|---|---|---|
| 核心思想 | 先操作,冲突时重试 | 先获取锁,再操作 |
| 阻塞性 | 非阻塞 | 阻塞 |
| 性能 | 低竞争下,性能极高 | 高竞争下,可能更稳定 |
| 缺点 | ABA问题、CPU空转 | 死锁、性能开销大 |
| 适用场景 | 计数器、栈顶/队头更新等简单操作 | 复杂的临界区操作 |
总而言之,CAS 是一种强大的低级原语,它为实现高效、无锁的并发算法提供了可能。 虽然直接使用它的场景有限,但理解其原理对于使用 java.util.concurrent 包、理解数据库的 MVCC 机制以及设计高性能系统都至关重要。
CAS(Compare and Swap)。这是一个在并发编程中至关重要的核心概念。
1. 什么是 CAS?
CAS,全称 Compare and Swap(比较并交换),是一种用于实现多线程同步的原子操作。
它的核心思想是:“我认为V的值应该是A,如果是,那么将它更新为B;否则,不要修改它,但要告诉我现在实际的值是多少。”
一个 CAS 操作包含三个操作数:
2. CAS 的工作原理
CAS 的运作流程可以用以下伪代码来理解:
boolean compareAndSwap(MemoryAddress V, int A, int B) {
if (*V == A) { // 步骤1:比较
*V = B; // 步骤2:交换
return true;
} else {
return false;
}
}
具体步骤:
V 的当前值是否与预期原值 A 相等。B,并返回 true 表示操作成功。V 的值。操作失败,不会进行任何更新,并返回 false。此时,调用者可以获取当前的最新值,并重新尝试操作。关键在于,整个“比较-交换”过程是一个单一的、不可分割的硬件原子指令。这意味着在操作过程中,不会被其他线程中断,从而保证了多线程环境下的数据安全。
3. 一个生动的例子
假设一个共享变量 count 的初始值为 5。
线程A 和 线程B 都想将其增加 1。
线程A 执行 CAS 操作:
V = &count, A = 5, B = 6CAS(&count, 5, 6) -> 成功!count 现在为 6。几乎同时,线程B 也试图执行 CAS 操作:
V = &count, A = 5, B = 6 (注意:线程B读取到的预期原值还是旧的 5)CAS(&count, 5, 6) -> 失败!因为 count 的当前值 (6) 不等于它的预期原值 (5)。线程B 的操作失败后,它会进行“自旋”:
count 的最新值 (6)。CAS(&count, 6, 7)。count 被更新为 7。这个过程就像是一个乐观的并发控制策略:我先假定没什么人和我竞争,如果事实证明我错了,那我就再试一次。
4. CAS 在 Java 中的实现
在 Java 中,CAS 操作是通过 sun.misc.Unsafe 类中的本地方法(Native Methods)实现的。这些方法最终会调用处理器底层的原子指令。
对于我们普通开发者来说,最直接的接触点是 java.util.concurrent.atomic 包下的原子类,例如:
AtomicIntegerAtomicLongAtomicReferenceAtomicStampedReference (用于解决 ABA 问题)示例:使用 AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet(); // 底层使用 CAS 实现
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet(); // 底层使用 CAS 实现
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + count.get()); // 总是 20000
}
}
incrementAndGet() 的内部实现就是一个典型的 CAS 自旋:
public final int incrementAndGet() {
for (;;) { // 自旋循环
int current = get(); // 获取当前值
int next = current + 1; // 计算新值
if (compareAndSet(current, next)) { // 尝试 CAS
return next; // 成功则返回
}
// 失败则立即重试
}
}
5. CAS 的优缺点
优点:
synchronized),CAS 避免了线程的阻塞和唤醒,这是一种在用户态完成的轻量级操作,减少了内核态和用户态切换的开销。在竞争不激烈的情况下,性能远高于锁。缺点:
ABA 问题:
AtomicStampedReference,它不仅在更新值时比较引用,还会比较一个 int 类型的版本戳(Stamp)。循环时间长开销大:
只能保证一个共享变量的原子操作:
AtomicReference 来保证这个复合对象的原子性。总结
| 特性 | 描述 |
|---|---|
| 核心思想 | 无锁编程、乐观锁。先尝试更新,如果失败就重试。 |
| 操作原子性 | 由硬件(CPU)指令保证,是现代多核处理器并发的基础。 |
| 在 Java 中 | 通过 Unsafe 类和 java.util.concurrent.atomic 包下的原子类暴露给开发者。 |
| 主要优势 | 高性能,在低到中度竞争环境下表现优异。 |
| 主要挑战 | ABA 问题、自旋导致的 CPU 开销、只能保证单个变量的原子性。 |
CAS 是现代并发包(如 Java 的 java.util.concurrent)的基石,理解了 CAS,就理解了为什么在高并发场景下,无锁算法和数据结构能够如此高效。
CAS (Compare and Swap) 详解
什么是 CAS
CAS(Compare and Swap,比较并交换)是一种无锁的原子操作,用于实现多线程环境下的同步机制。它通过硬件指令保证操作的原子性,是现代并发编程中的重要基础。
CAS 工作原理
基本操作流程
CAS(V, E, N)
- V: 要更新的内存位置
- E: 期望的旧值
- N: 要设置的新值
操作步骤:
伪代码实现
bool CAS(int* ptr, int expected, int new_value) {
if (*ptr == expected) {
*ptr = new_value;
return true;
}
return false;
}
CAS 的特点
优点
缺点
Java 中的 CAS 实现
Atomic 类使用示例
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
int oldValue, newValue;
do {
oldValue = count.get(); // 获取当前值
newValue = oldValue + 1; // 计算新值
} while (!count.compareAndSet(oldValue, newValue)); // CAS 操作
}
public int getCount() {
return count.get();
}
}
简化写法
public class SimpleCASExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 内部使用 CAS 实现
}
}
解决 ABA 问题
使用版本号/标记
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABAExample {
private AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(0, 0);
public void updateValue(int expectedValue, int newValue) {
int[] stampHolder = new int[1];
int currentValue = atomicRef.get(stampHolder);
int currentStamp = stampHolder[0];
// 比较值和版本号
if (currentValue == expectedValue) {
atomicRef.compareAndSet(expectedValue, newValue,
currentStamp, currentStamp + 1);
}
}
}
CAS 底层实现
硬件支持
CMPXCHG 指令LDREX/STREX 指令对Java 中的 Unsafe 类
// Java 底层通过 Unsafe 类实现 CAS
public final class AtomicInteger extends Number {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
实际应用场景
1. 计数器
public class Counter {
private AtomicLong count = new AtomicLong(0);
public long increment() {
return count.incrementAndGet();
}
}
2. 非阻塞栈
public class ConcurrentStack<E> {
private AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = top.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
final E item;
Node<E> next;
Node(E item) {
this.item = item;
}
}
}
3. 乐观锁
public class OptimisticLockExample {
private AtomicInteger version = new AtomicInteger(0);
private String data;
public boolean updateData(String newData, int expectedVersion) {
// 检查版本号是否变化
if (version.compareAndSet(expectedVersion, expectedVersion + 1)) {
this.data = newData;
return true;
}
return false; // 版本冲突,更新失败
}
}
性能考虑
适用场景
不适用场景
总结
CAS 是现代并发编程的核心技术之一,它提供了高效的无锁同步机制。虽然存在 ABA 问题和高竞争下的性能问题,但在合适的场景下,CAS 能够显著提升程序性能。理解 CAS 的原理和适用场景对于编写高性能的并发程序至关重要。
我们来对 CAS(Compare and Swap) 进行一次详细的解析。这是一个在并发编程中至关重要的核心概念。
一、什么是 CAS?
CAS,全称 Compare and Swap,中文可译为“比较并交换”。它是一种原子操作,用于在多线程环境下实现无锁(lock-free)的线程安全。
它的核心思想是:“我认为V的值应该是A,如果是,那么将V的值更新为B;否则,不修改并告诉我当前V的实际值是多少。”
二、CAS 的工作原理
一个 CAS 操作包含三个操作数:
其执行流程可以用以下伪代码来理解:
function CAS(V, A, B) is
// 1. 读取内存位置V的当前值
temp = *V
// 2. 比较当前值是否与预期原值A相等
if temp == A then
// 3. 如果相等,说明内存值未被其他线程修改,则将新值B写入V
*V = B
// 4. 返回成功
return true
else
// 5. 如果不相等,说明内存值已经被其他线程修改,则操作失败。返回当前的实际值(或失败状态)
return false (and optionally return temp)
这个过程是原子性的,意味着在比较和交换之间,不会有其他线程插入来修改内存位置V的值。
一个生动的比喻:
假设一个共享储物柜(内存位置 V),你认为里面放的是你的书(预期原值 A),你想用你的笔记本电脑(新值 B)换回这本书。
但是,如果在你看储物柜(步骤1)和准备交换(步骤3)的瞬间,你的朋友过来把他的书拿走了,并放了一个水杯进去。那么:
三、CAS 的底层硬件实现
CAS 之所以能成为原子操作,并不是通过软件锁实现的,而是依赖于底层硬件(主要是CPU)提供的指令。
CMPXCHG 指令(Compare and Exchange)。LDREX 和 STREX 指令对,用于实现类似的加载-链接/条件存储操作。现代的多核处理器都在硬件层面直接支持这些原子操作指令,确保了它们在多核环境下的原子性。
四、CAS 在 Java 中的应用
在 Java 中,CAS 操作是由 sun.misc.Unsafe 类中的本地方法(Native Method)提供的,例如 compareAndSwapInt, compareAndSwapLong 等。
然而,对于普通开发者来说,我们通常不会直接使用 Unsafe 类。而是通过 java.util.concurrent.atomic 包下的原子类来间接使用 CAS。
最典型的例子:AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
// 这行代码底层就使用了 CAS
count.incrementAndGet();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + count.get()); // 总是 20000
}
}
incrementAndGet() 的内部实现,就是一个典型的 CAS + 自旋 循环:
public final int incrementAndGet() {
// 自旋循环
for (;;) {
int current = get(); // 获取当前值,作为预期原值 A
int next = current + 1; // 计算新值 B
// 调用 CAS:如果 count 的当前值还是 current,就把它更新为 next
if (compareAndSet(current, next)) {
return next; // 成功则返回新值
}
// 失败则循环重试,重新读取最新的 current 值
}
}
五、CAS 的三大经典问题
尽管 CAS 非常高效,但它也存在一些著名的缺陷:
1. ABA 问题
AtomicStampedReference(维护一个版本号戳)和 AtomicMarkableReference(维护一个布尔标记)来解决这个问题。2. 循环时间长开销大
for 循环会不停地重试,会占用大量的 CPU 资源。3. 只能保证一个共享变量的原子操作
AtomicReference 来保证引用类型对象的原子性,从而间接实现多个变量的原子更新。六、CAS 的优缺点总结
优点:
synchronized)。缺点:
七、总结
CAS 是现代并发编程的基石之一。它通过利用硬件的原子指令,提供了一种高效的无锁同步机制。理解 CAS 是理解 java.util.concurrent 包中众多高效工具类(如 ConcurrentHashMap, AQS 等)的关键。
它并非万能的银弹,开发者需要根据具体的竞争强度和业务场景,在 CAS、锁 和 其他并发工具 之间做出权衡。
CAS (Compare and Swap) 详解
什么是 CAS?
CAS(Compare and Swap,比较并交换) 是一种无锁的原子操作,用于在多线程环境下实现同步而不使用传统的锁机制。它是现代并发编程的基石之一。
CAS 操作原理
CAS 操作包含三个基本操作数:
操作过程:
如果内存位置 V 的值等于期望值 A,则将该位置的值更新为新值 B
否则,不进行任何操作
CAS 伪代码
bool CAS(int* ptr, int expected, int new_value) {
if (*ptr == expected) {
*ptr = new_value;
return true;
}
return false;
}
CAS 的特点
原子性 CAS 操作是硬件级别的原子操作,不会被线程调度机制打断。
无锁编程 CAS 是实现无锁(lock-free)数据结构的基础。
乐观锁机制 采用"先尝试,失败重试"的策略,而不是悲观地获取锁。
Java 中的 CAS 实现
Atomic 类使用 CAS
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
int oldValue;
int newValue;
do {
oldValue = count.get(); // 读取当前值
newValue = oldValue + 1; // 计算新值
} while (!count.compareAndSet(oldValue, newValue)); // CAS 操作
}
public int getCount() {
return count.get();
}
}
更简洁的写法
public void incrementSimple() {
count.incrementAndGet(); // 内部使用 CAS
}
CAS 的典型应用
1. 计数器
AtomicInteger counter = new AtomicInteger(0);
// 线程安全的自增
counter.incrementAndGet();
// 线程安全的加法
counter.addAndGet(5);
2. 标志位控制
AtomicBoolean flag = new AtomicBoolean(false);
// 只有一个线程能成功设置为 true
if (flag.compareAndSet(false, true)) {
// 执行初始化操作
}
3. 引用类型原子更新
AtomicReference<String> reference = new AtomicReference<>("initial");
// 原子更新引用
reference.compareAndSet("initial", "updated");
CAS 的优缺点
优点
缺点
解决 ABA 问题
版本号机制
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABASolution {
private AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(0, 0);
public void update(int expectedValue, int newValue) {
int[] stampHolder = new int[1];
int currentValue = atomicRef.get(stampHolder);
int currentStamp = stampHolder[0];
if (currentValue == expectedValue) {
atomicRef.compareAndSet(currentValue, newValue,
currentStamp, currentStamp + 1);
}
}
}
CAS 底层实现
CPU 硬件支持
CMPXCHG 指令LDREX/STREX 指令对Java 中的实现
// Unsafe 类提供 CAS 操作
public final class Unsafe {
public final native boolean compareAndSwapObject(
Object o, long offset, Object expected, Object x);
public final native boolean compareAndSwapInt(
Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(
Object o, long offset, long expected, long x);
}
实际应用示例
无锁栈实现
import java.util.concurrent.atomic.AtomicReference;
public class LockFreeStack<T> {
private static class Node<T> {
final T value;
Node<T> next;
Node(T value) {
this.value = value;
}
}
private AtomicReference<Node<T>> top = new AtomicReference<>();
public void push(T value) {
Node<T> newHead = new Node<>(value);
Node<T> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public T pop() {
Node<T> oldHead;
Node<T> newHead;
do {
oldHead = top.get();
if (oldHead == null) {
return null;
}
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.value;
}
}
性能考虑
适用场景
不适用场景
CAS 是现代并发编程的重要工具,理解其原理和适用场景对于编写高性能的并发程序至关重要。
CAS (Compare and Swap) 详解
什么是CAS
CAS(Compare and Swap,比较并交换)是一种无锁(lock-free)的并发控制机制,用于在多线程环境中实现原子操作。它是现代并发编程中重要的同步原语之一。
CAS的基本原理
CAS操作包含三个基本操作数:
CAS的语义是:只有当内存位置V的值等于预期原值A时,才会将V的值更新为新值B,否则不执行任何操作。无论是否更新成功,都会返回当前V的实际值。
CAS的工作流程
CAS的伪代码表示
function compareAndSwap(V, A, B):
if V == A:
V = B
return true
else:
return false
CAS的优点
CAS的缺点
ABA问题及解决方案
ABA问题示例
解决方案
AtomicStampedReferenceAtomicMarkableReferenceJava中的CAS实现
Java提供了java.util.concurrent.atomic包来支持CAS操作:
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
int oldValue;
int newValue;
do {
oldValue = counter.get();
newValue = oldValue + 1;
} while (!counter.compareAndSet(oldValue, newValue));
}
public int get() {
return counter.get();
}
}
实际应用场景
与锁的比较
| 特性 | CAS | 传统锁 |
|---|---|---|
| 性能 | 低竞争时高,高竞争时可能下降 | 高竞争时性能下降明显 |
| 实现复杂度 | 较高 | 较低 |
| 死锁风险 | 无 | 有 |
| 适用场景 | 低竞争、短临界区 | 高竞争、长临界区 |
现代CPU对CAS的支持
大多数现代CPU都提供了CAS指令:
CMPXCHG (Compare and Exchange)LDREX/STREX指令对lwarx/stwcx指令对这些硬件指令保证了CAS操作的原子性。
总结
CAS是一种强大的无锁同步机制,适用于低竞争场景下的并发编程。它通过硬件指令保证原子性,避免了传统锁的开销,但需要开发者注意ABA问题和自旋开销等问题。在现代并发编程中,CAS是构建高性能、可伸缩并发应用的重要工具。
CAS(Compare and Swap)详细解释
1. 基本定义与原理 CAS(比较并交换)是一种无锁并发控制技术,用于实现多线程环境下的原子操作。其核心包含三个操作数:
操作逻辑:
if V == A then
V = B // 更新成功
else
重试或放弃 // 更新失败
该操作由单条CPU指令(如x86的CMPXCHG)原子性完成,确保比较和更新过程不可分割。
2. 工作流程
以两个线程竞争修改共享变量V=10为例:
V=10CAS(V, 10, 20):
V==10(匹配)→ 更新V=20成功CAS(V, 10, 30):
V=20≠10(不匹配)→ 更新失败 → 重新读取V=20并自旋重试3. 核心特点
| 优势 | 劣势 |
|---|---|
| 无锁算法:避免线程阻塞和上下文切换开销 | ABA问题:值从A→B→A变化时,CAS误判未修改 |
| 乐观锁:假设冲突概率低,直接尝试更新 | 自旋消耗CPU:竞争激烈时循环重试浪费资源 |
| 原子性:硬件级指令保证操作不可分割 | 仅适用简单操作:复杂逻辑需配合其他机制 |
4. 典型应用场景
AtomicInteger,通过CAS实现incrementAndGet()
// 伪代码实现
do {
int oldValue = get(); // 读取当前值
int newValue = oldValue + 1;
} while (!CAS(oldValue, newValue)); // 自旋直到成功
5. ABA问题解决方案 通过添加版本号/时间戳扩展CAS操作:
A(A, Version=1)if (V.value == A && V.version == 1)
then update to (B, 2)
例如Java的AtomicStampedReference。相关问题
CAS(Compare and Swap,比较并交换)是一种无锁同步机制,核心是通过硬件指令保证操作的原子性,实现多线程下对共享变量的安全修改,无需依赖锁。
一、CAS的核心工作原理 CAS操作依赖三个关键参数,通过“比较-交换”的原子步骤完成变量更新,具体流程如下:
cmpxchg指令)原子性地比较“内存地址V中的实际值”与“预期值A”。二、CAS的关键特性(优缺点) CAS通过无锁设计解决了传统锁的开销问题,但也存在固有局限,具体如下:
1. 优点
cmpxchg、ARM的ldrex/strex),天然保证“比较-交换”步骤的原子性,无需额外同步。2. 缺点
AtomicInteger的getAndIncrement)。volatile或其他机制)。三、ABA问题的解决方案 针对ABA问题,主流方案是为变量增加“版本号”或“时间戳”,让变量的“值+版本号”共同作为判断依据,具体实现如下:
AtomicStampedReference,通过“对象引用+整数戳”的组合,确保即使值相同,版本号不同也会判定为修改,避免ABA问题。四、CAS的实际应用场景 CAS是很多并发工具的底层实现,常见应用包括:
java.util.concurrent.atomic包(如AtomicInteger、AtomicLong),通过CAS实现变量的原子增减、更新。ConcurrentLinkedQueue(无锁队列),通过CAS实现节点的入队、出队操作,避免锁竞争。要不要我帮你整理一份CAS核心知识点脑图?把工作原理、优缺点、解决方案和应用场景按逻辑串联,方便你快速梳理和记忆。
CAS(Compare and Swap,比较并交换)是一种无锁并发编程的核心技术,广泛应用于多线程环境下的原子操作。它通过硬件指令保证操作的原子性,避免了传统锁机制带来的性能开销和死锁风险。
一、CAS 的基本概念
定义: CAS 是一种原子操作,用于实现多线程环境下的无锁更新。
操作逻辑: CAS 操作包含三个参数:
操作语义:
if V == A:
V = B
return true
else:
return false
只有当 V 的当前值等于预期值 A 时,才会将 V 更新为 B,否则不做任何操作。
二、CAS 的底层支持
CAS 操作依赖于CPU 提供的原子指令,例如:
| 平台 | 指令 |
|---|---|
| x86 | CMPXCHG |
| ARM | LDREX/STREX |
这些指令保证了 CAS 操作的原子性,即使在多核环境下也不会出现竞态条件。
三、CAS 的应用场景
原子类(Java 中的 AtomicInteger , AtomicReference 等)
AtomicInteger count = new AtomicInteger(0);
count.compareAndSet(0, 1); // 如果当前值是0,则更新为1
无锁数据结构
自旋锁(Spinlock) 使用 CAS 实现简单的自旋锁:
int lock = 0;
void acquire() {
while (!CAS(&lock, 0, 1)); // 自旋等待
}
void release() {
lock = 0;
}
四、CAS 的优点
| 优点 | 说明 |
|---|---|
| 无锁 | 避免死锁、线程阻塞 |
| 高性能 | 无需内核调度,用户态完成 |
| 可扩展 | 适合高并发场景 |
五、CAS 的缺点与问题
1. ABA 问题
问题描述:
线程 T1 读取值为 A,被挂起;线程 T2 将 A → B → A;T1 恢复后认为值没变,CAS 成功,但实际上中间状态已变。
解决方案:
AtomicStampedReference)2. 自旋开销 CAS 失败时会重试,可能导致忙等待(busy waiting),浪费 CPU。
3. 仅保证单个变量原子性 CAS 只能操作一个变量,无法原子地更新多个变量。
六、CAS 与锁的对比
| 特性 | CAS | 锁 |
|---|---|---|
| 是否阻塞 | 不阻塞 | 可能阻塞 |
| 死锁风险 | 无 | 有 |
| 性能 | 高并发下更好 | 低并发下稳定 |
| 复杂度 | 高(ABA、重试) | 低 |
| 适用范围 | 原子变量、无锁结构 | 任意临界区 |
七、总结一句话
拓展阅读(可选)
sun.misc.Unsafe.compareAndSwapInt()std::atomic<T>::compare_exchange_weak()cmpxchg() 宏实现如果你有具体语言(如 Java、C++、Rust)或场景(如队列、计数器)想深入,我可以给你写代码示例。
CAS(Compare and Swap)详细解释
1. 基本定义与原理 CAS(比较并交换)是一种无锁并发控制技术,用于实现多线程环境下的原子操作。其核心包含三个操作数:
操作逻辑:
if V == A then
V = B // 更新成功
else
重试或放弃 // 更新失败
该操作由单条CPU指令(如x86的CMPXCHG)原子性完成,确保比较和更新过程不可分割。
2. 工作流程
以两个线程竞争修改共享变量V=10为例:
V=10CAS(V, 10, 20):
V==10(匹配)→ 更新V=20成功CAS(V, 10, 30):
V=20≠10(不匹配)→ 更新失败 → 重新读取V=20并自旋重试3. 核心特点
| 优势 | 劣势 |
|---|---|
| 无锁算法:避免线程阻塞和上下文切换开销 | ABA问题:值从A→B→A变化时,CAS误判未修改 |
| 乐观锁:假设冲突概率低,直接尝试更新 | 自旋消耗CPU:竞争激烈时循环重试浪费资源 |
| 原子性:硬件级指令保证操作不可分割 | 仅适用简单操作:复杂逻辑需配合其他机制 |
4. 典型应用场景
AtomicInteger,通过CAS实现incrementAndGet()
// 伪代码实现
do {
int oldValue = get(); // 读取当前值
int newValue = oldValue + 1;
} while (!CAS(oldValue, newValue)); // 自旋直到成功
5. ABA问题解决方案 通过添加版本号/时间戳扩展CAS操作:
A(A, Version=1)if (V.value == A && V.version == 1)
then update to (B, 2)
例如Java的AtomicStampedReference。相关问题
CAS(Compare-And-Swap,比较并交换)是一种非常重要的无锁(Lock-Free)并发编程技术,它通过硬件原子指令实现多线程环境下的安全数据操作,避免了传统锁机制的开销。
下面是一个核心概览表,帮你快速了解CAS:
| 方面 | 核心要点 |
|---|---|
| 核心思想 | 我认为内存位置 V 的值应该是 A。如果是,我就把它更新为 B;如果不是,我就不更新(通常会重试)。整个过程是原子的。 |
| 操作数 | 内存位置 (V)、预期原值 (A)、新值 (B) |
| 底层支撑 | CPU硬件指令(如x86的 CMPXCHG) |
| 本质 | 一种乐观锁实现 |
| 关键优点 | 无锁,减少线程阻塞和上下文切换,在高并发低竞争场景下性能优异 |
| 经典问题 | ABA问题(可通过AtomicStampedReference带版本号解决) |
| 常见应用 | AtomicInteger等原子类、自旋锁、无锁队列、乐观锁机制 |
工作原理与步骤
CAS操作是一个原子指令,其执行流程如下:
V 的当前值,作为预期值 A。V 的当前值是否仍等于预期值 A。
V 的值更新为新值 B。其基本流程可概括为:
读取V的值 -> 比较V的值是否等于A -> [等于] 则将V的值更新为B -> 返回成功
-> [不等于] 返回失败(通常伴随重试)
️ 在Java中的实现
Java主要通过 sun.misc.Unsafe 类(底层调用Native方法)和 java.util.concurrent.atomic 包下的原子类(如 AtomicInteger, AtomicLong, AtomicReference)来提供CAS操作。
例如 AtomicInteger 的 incrementAndGet() 方法底层实现:
public final int incrementAndGet() {
int current;
do {
current = get(); // 获取当前值
// 循环尝试CAS,直到成功为止
} while (!compareAndSet(current, current + 1));
return current + 1;
}
AtomicInteger 内部使用了一个 volatile 修饰的 value 字段来保证可见性,并通过 Unsafe.compareAndSwapInt 方法实现CAS操作,该方法参数包括对象实例、字段内存偏移量、预期值和新值。
CAS的优劣势
AtomicStampedReference)。AtomicReference 来引用。主要应用场景
AtomicInteger, AtomicLong 等原子类,其核心实现依赖于CAS。ConcurrentLinkedQueue)、无锁栈等,利用CAS实现线程安全的插入和删除。️ 使用注意事项
AtomicStampedReference 等带版本号的原子类来规避风险。java.util.concurrent 包中的类),而非直接操作CAS。总结
CAS是理解现代高并发编程的基石。它通过硬件辅助的原子操作,巧妙地避免了锁的诸多开销,在特定场景下能带来巨大的性能提升。
理解CAS的核心在于掌握其乐观并发的思想:先冒险操作,遇到冲突再重试。同时,务必警惕其著名的 ABA问题。
希望这份详解对你有帮助。
如果你想了解特定并发场景下是选择CAS还是锁更合适,或者想深入了解无锁数据结构的设计,我很乐意继续与你探讨。
CAS(Compare and Swap,比较并交换)是一种无锁并发编程的核心机制,通过硬件指令实现原子操作,确保多线程环境下共享数据的一致性。以下是其核心原理、应用场景及局限性的综合分析:
一、CAS 的核心原理
操作逻辑
CAS 包含三个参数:内存地址(V)、预期值(A)、新值(B)。其执行流程为:
cmpxchg)保证,不可被中断。硬件支持
cmpxchg、ARM 的 ldrex/strex 等指令直接实现 CAS,通过锁总线或缓存一致性协议(如 MESI)保证原子性。sun.misc.Unsafe 类的 compareAndSwapInt 等方法调用底层指令,原子类(如 AtomicInteger)封装了这些操作。二、CAS 的优势
无锁并发
AtomicInteger.incrementAndGet() 通过自旋 CAS 实现线程安全递增。高性能
synchronized),减少线程阻塞和调度成本。避免死锁
三、CAS 的局限性及解决方案
ABA 问题
AtomicStampedReference,通过版本号跟踪变量状态。AtomicMarkableReference 标记变量是否被修改。自旋开销
Thread.yield())。单一变量限制
AtomicReference 操作整体。ConcurrentHashMap 的分段锁)。四、CAS 的应用场景
原子计数器
AtomicInteger 实现线程安全计数,避免锁的开销。无锁数据结构
ConcurrentLinkedQueue)、栈等,通过 CAS 实现并发安全。状态标志更新
AtomicBoolean 控制线程任务启停,确保状态一致性。乐观锁机制
SETNX)均基于 CAS 思想。五、CAS 与锁的对比
| 特性 | CAS(乐观锁) | 锁(悲观锁) |
|---|---|---|
| 核心思想 | 假设无冲突,失败后重试 | 假设必有冲突,先加锁再操作 |
| 线程状态 | 无阻塞,自旋等待 | 阻塞等待锁释放 |
| 性能 | 低冲突时高效,高冲突时性能下降 | 高冲突时稳定,低冲突时开销大 |
| 适用场景 | 读多写少、操作简单 | 写多读少、临界区复杂 |
| ABA 问题 | 存在(需额外解决) | 不存在 |
六、总结
CAS 是无锁并发的基石,通过硬件指令实现原子操作,在低冲突场景下显著提升性能。但其局限性(如 ABA 问题、自旋开销)需结合版本号、对象封装等方案解决。实际开发中,应根据场景选择:低竞争用 CAS,高竞争用锁,例如 JDK 的 LongAdder 在高并发下分段累加,平衡性能与准确性。
CAS(Compare and Swap,比较并交换)是并发编程中一种非常重要的无锁(Lock-Free)原子操作,它被广泛应用于实现多线程同步(如实现自旋锁、乐观锁)和构建非阻塞数据结构。下面我将从多个角度为你详细解析它。
一、核心思想与原理
核心思想: “我认为值应该是A,如果是,我就把它改成B;如果不是,说明有别人改过了,那我就不修改了。”
这是一个原子操作,意味着比较和交换这两个步骤在执行时是不可分割的,中间不会被打断。
操作数: CAS 操作通常需要三个参数:
伪代码逻辑:
function CAS(memory_address, expected_value, new_value) {
if (*memory_address == expected_value) {
*memory_address = new_value;
return true; // 成功
} else {
return false; // 失败
}
}
(注意:实际的CPU指令将整个if判断和赋值作为一个不可中断的指令执行)
二、底层实现
CAS 的实现高度依赖于硬件。现代大多数CPU架构(如 x86、ARM)都提供了实现CAS的指令:
CMPXCHG (Compare and Exchange) 指令。LDREX 和 STREX 指令组合。高级编程语言(如 Java、C++)通过调用这些底层硬件指令来提供CAS操作。
sun.misc.Unsafe 类中有 compareAndSwapInt, compareAndSwapLong 等方法。但我们通常不直接使用它,而是使用封装好的原子类,如 AtomicInteger, AtomicReference 等。std::atomic 及其相关的 compare_exchange_strong 和 compare_exchange_weak 成员函数。三、一个简单的Java示例
Java中的 AtomicInteger 是使用CAS的经典例子。
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
public static void main(String[] args) {
AtomicInteger atomicInt = new AtomicInteger(0); // 初始值为0
// 线程1:尝试将 0 -> 1
boolean success1 = atomicInt.compareAndSet(0, 1); // CAS操作
System.out.println("CAS(0, 1): " + success1 + ", value: " + atomicInt.get());
// 线程2:尝试将 0 -> 2 (但此时值已经是1了,所以会失败)
boolean success2 = atomicInt.compareAndSet(0, 2); // CAS操作
System.out.println("CAS(0, 2): " + success2 + ", value: " + atomicInt.get());
}
}
输出:
CAS(0, 1): true, value: 1
CAS(0, 2): false, value: 1
这个例子清晰地展示了CAS的语义:只有在值符合预期时才会修改成功。
四、优点与应用场景
性能优势( vs. 锁 ):
经典应用场景:
AtomicInteger, AtomicLong, AtomicReference 等。五、经典问题与局限性
ABA 问题
AtomicStampedReference 就是为此而生的。循环时间长开销大
只能保证一个共享变量的原子操作
AtomicReference 来保证这个对象引用的原子性。总结
| 特性 | 描述 |
|---|---|
| 核心 | 无锁的原子操作,通过硬件指令保证“比较-交换”的原子性。 |
| 优点 | 高性能、非阻塞、避免死锁。 |
| 缺点 | ABA问题、自旋CPU开销、只能保证一个变量的原子性。 |
| 应用 | 原子类、乐观锁、无锁数据结构。 |
| 本质 | 一种乐观并发策略,假设冲突很少发生,发生冲突后再重试。 |
CAS是现代并发编程的基石之一,它使得我们能够在不使用重量级锁的情况下,安全地管理共享数据,极大地提升了多线程程序的性能和可伸缩性。理解CAS是深入理解Java JUC(java.util.concurrent)包或其他语言并发库的关键。
以下是 CAS(Compare and Swap)详解:
一、核心概念与原理
基本定义
乐观锁特性
二、底层实现与技术支撑
硬件基础
cmpxchg指令;LDREX/STREX指令对[^1^][^3^][^5^]。Java中的封装
sun.misc.Unsafe类的本地方法(如compareAndSwapInt())直接调用CPU指令[^1^][^2^][^3^]。Unsafe类限制访问,通常通过JUC包中的原子类(如AtomicInteger)间接使用CAS能力[^1^][^2^][^3^]。三、典型应用场景
高性能计数器
AtomicInteger的incrementAndGet()方法,通过自旋+CAS循环实现线程安全递增[^2^][^3^][^4^]。自定义自旋锁
SpinLock类中通过AtomicReference<Thread>存储持有者信息[^2^][^4^]。无锁数据结构
四、关键问题与解决方案
ABA问题
自旋开销与CPU消耗
Thread.yield()或LockSupport.parkNanos()让出CPU[^3^][^5^]。单变量原子性限制
AtomicReference包装复合对象,或改用互斥锁[^3^][^5^]。五、CAS与其他同步机制对比
| 特性 | CAS | Synchronized/Mutex |
|---|---|---|
| 阻塞行为 | 无阻塞(忙等待) | 阻塞(线程挂起) |
| 性能 | 低竞争场景更优 | 高竞争场景更优 |
| CPU消耗 | 高竞争时较高 | 上下文切换开销大 |
| 死锁风险 | 无 | 存在 |
| 适用场景 | 简单操作、低竞争 | 复杂临界区、高竞争 |
综上所述,CAS作为一种轻量级的无锁同步机制,通过硬件原子指令实现了高效的并发控制。其在低竞争场景下性能优异,但也需注意ABA问题、自旋开销及单变量限制等陷阱。合理选择CAS与互斥锁,需根据实际场景权衡性能与复杂度。
CAS(Compare and Swap),即“比较并交换”,是一种用于实现多线程同步的原子操作,常用于无锁(lock-free)编程中,是实现线程安全操作的重要机制之一。
一、CAS 的基本原理
CAS 操作包含三个操作数:
CAS 的执行逻辑如下:
用伪代码表示如下:
boolean compareAndSwap(int *V, int A, int B) {
if (*V == A) {
*V = B;
return true; // 成功更新
} else {
return false; // 值已被修改,更新失败
}
}
二、CAS 的应用场景
原子变量(如 AtomicInteger):
Java 中的 java.util.concurrent.atomic 包提供了基于 CAS 实现的原子类,例如 AtomicInteger、AtomicLong、AtomicReference 等。
无锁队列/栈: 利用 CAS 实现线程安全的队列、栈等数据结构,避免使用传统锁机制,提高并发性能。
自旋锁(SpinLock): 基于 CAS 实现的一种轻量级锁机制,适用于锁竞争不激烈的场景。
三、CAS 的优点
四、CAS 的缺点
ABA 问题: 如果变量 V 的值从 A 被修改为 B,再改回 A,CAS 会误认为它从未被修改过。
解决方案:
使用版本号(如 Java 中的 AtomicStampedReference)或时间戳来区分不同状态。
只能保证一个变量的原子性:
如果需要对多个变量进行原子操作,需要额外封装(如使用 AtomicReference 包装多个变量)。
自旋开销大: 如果 CAS 多次失败,线程会不断重试(自旋),造成 CPU 资源浪费。
五、Java 中的实现示例
以 AtomicInteger 为例:
AtomicInteger atomicInt = new AtomicInteger(0);
// 使用 CAS 实现递增
int oldValue = atomicInt.get();
while (!atomicInt.compareAndSet(oldValue, oldValue + 1)) {
oldValue = atomicInt.get();
}
六、总结
CAS 是一种高效的并发控制机制,适用于读多写少、竞争不激烈的场景。但需注意 ABA 问题、只能操作单个变量等局限性。现代并发编程中,CAS 是构建高性能、无锁数据结构的基础。
CAS 的三大经典问题
1. ABA 问题
问题描述 ABA 问题是 CAS 操作中最经典的问题。它发生在以下情况:
虽然最终值相同,但中间经历了状态变化,可能导致逻辑错误。
实际场景示例
public class ABAProblem {
private AtomicReference<String> stackTop = new AtomicReference<>("A");
public void push(String value) {
String oldTop;
do {
oldTop = stackTop.get();
// 模拟其他操作
} while (!stackTop.compareAndSet(oldTop, value));
}
public String pop() {
String oldTop;
String newTop;
do {
oldTop = stackTop.get();
if (oldTop == null) return null;
// 获取下一个节点
newTop = getNextNode(oldTop);
} while (!stackTop.compareAndSet(oldTop, newTop));
return oldTop;
}
// 线程1:读取栈顶为 "A"
// 线程2:pop() 将栈顶从 "A" -> "B"
// 线程2:push("A") 将栈顶从 "B" -> "A"
// 线程1:CAS 成功,但实际上栈结构已发生变化
}
解决方案
使用 AtomicStampedReference
public class ABASolution {
private AtomicStampedReference<String> stackTop =
new AtomicStampedReference<>("A", 0);
public boolean push(String value) {
int[] stampHolder = new int[1];
String oldTop;
int oldStamp;
do {
oldTop = stackTop.get(stampHolder);
oldStamp = stampHolder[0];
// 检查是否有其他修改
} while (!stackTop.compareAndSet(oldTop, value, oldStamp, oldStamp + 1));
return true;
}
public String pop() {
int[] stampHolder = new int[1];
String oldTop;
int oldStamp;
String newTop;
do {
oldTop = stackTop.get(stampHolder);
oldStamp = stampHolder[0];
if (oldTop == null) return null;
newTop = getNextNode(oldTop);
} while (!stackTop.compareAndSet(oldTop, newTop, oldStamp, oldStamp + 1));
return oldTop;
}
}
使用 AtomicMarkableReference
public class ABASolution2 {
private AtomicMarkableReference<String> stackTop =
new AtomicMarkableReference<>("A", false);
public boolean push(String value) {
boolean[] markHolder = new boolean[1];
String oldTop;
do {
oldTop = stackTop.get(markHolder);
// 使用标记位来检测变化
} while (!stackTop.compareAndSet(oldTop, value, markHolder[0], !markHolder[0]));
return true;
}
}
2. 循环时间长开销大
问题描述 在高并发场景下,多个线程同时竞争同一个变量,导致大量线程不断重试 CAS 操作,造成 CPU 资源浪费。
性能影响
public class HighContentionExample {
private AtomicInteger counter = new AtomicInteger(0);
public void highContentionMethod() {
// 在高并发下,这个循环可能导致大量 CPU 消耗
while (!counter.compareAndSet(counter.get(), counter.get() + 1)) {
// 空转,消耗 CPU
}
}
}
解决方案
1. 退避策略 (Backoff)
public class BackoffStrategy {
private AtomicInteger counter = new AtomicInteger(0);
private final int maxDelay = 1000; // 最大延迟 1ms
public void incrementWithBackoff() {
int delay = 1;
while (true) {
int current = counter.get();
int next = current + 1;
if (counter.compareAndSet(current, next)) {
break; // 成功退出
}
// 指数退避
try {
Thread.sleep(0, delay); // 纳秒级休眠
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
delay = Math.min(delay * 2, maxDelay);
}
}
}
2. 使用 LongAdder (Java 8+)
import java.util.concurrent.atomic.LongAdder;
public class LowContentionSolution {
private LongAdder counter = new LongAdder();
public void increment() {
counter.increment(); // 内部使用分段计数,减少竞争
}
public long getCount() {
return counter.sum();
}
}
3. 分散热点
public class HotspotDistribution {
private final int CONCURRENCY_LEVEL = Runtime.getRuntime().availableProcessors();
private AtomicInteger[] counters;
public HotspotDistribution() {
counters = new AtomicInteger[CONCURRENCY_LEVEL];
for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
counters[i] = new AtomicInteger(0);
}
}
public void increment() {
int index = Thread.currentThread().hashCode() % CONCURRENCY_LEVEL;
if (index < 0) index = -index;
counters[index].incrementAndGet();
}
public int getTotal() {
int sum = 0;
for (AtomicInteger counter : counters) {
sum += counter.get();
}
return sum;
}
}
3. 只能保证一个共享变量的原子操作
问题描述 CAS 只能保证单个变量的原子性,对于需要同时更新多个相关变量的场景无法保证一致性。
典型场景
public class MultiVariableProblem {
private AtomicInteger x = new AtomicInteger(0);
private AtomicInteger y = new AtomicInteger(0);
// 需要原子性地更新 x 和 y
public void updateBoth(int newX, int newY) {
// 这两个操作不是原子的
x.set(newX);
y.set(newY);
// 在 set(newX) 和 set(newY) 之间,其他线程可能看到不一致的状态
}
public void checkConsistency() {
int currentX = x.get();
int currentY = y.get();
// 这里可能看到 x 和 y 的不一致状态
}
}
解决方案
1. 使用 AtomicReference 封装对象
public class MultiVariableSolution1 {
static class Point {
final int x;
final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
private AtomicReference<Point> point = new AtomicReference<>(new Point(0, 0));
public void updateBoth(int newX, int newY) {
Point current;
Point newPoint;
do {
current = point.get();
newPoint = new Point(newX, newY);
} while (!point.compareAndSet(current, newPoint));
}
public int getX() {
return point.get().x;
}
public int getY() {
return point.get().y;
}
}
2. 使用锁机制
public class MultiVariableSolution2 {
private int x = 0;
private int y = 0;
private final Object lock = new Object();
public void updateBoth(int newX, int newY) {
synchronized(lock) {
x = newX;
y = newY;
}
}
public void checkConsistency() {
synchronized(lock) {
int currentX = x;
int currentY = y;
// 保证看到一致的状态
}
}
}
3. 使用 VarHandle (Java 9+)
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class MultiVariableSolution3 {
private static class State {
int x;
int y;
}
private volatile State state = new State();
private static final VarHandle STATE_HANDLE;
static {
try {
STATE_HANDLE = MethodHandles.lookup()
.findVarHandle(MultiVariableSolution3.class, "state", State.class);
} catch (Exception e) {
throw new Error(e);
}
}
public void updateBoth(int newX, int newY) {
State current;
State newState = new State();
newState.x = newX;
newState.y = newY;
do {
current = state;
} while (!STATE_HANDLE.compareAndSet(this, current, newState));
}
}
综合解决方案示例
完整的线程安全计数器
public class CompleteCounter {
private final LongAdder baseCount = new LongAdder();
private final AtomicStampedReference<Long> lastUpdate =
new AtomicStampedReference<>(0L, 0);
public void increment() {
baseCount.increment();
// 原子更新最后更新时间戳
int[] stampHolder = new int[1];
Long oldValue;
int oldStamp;
do {
oldValue = lastUpdate.get(stampHolder);
oldStamp = stampHolder[0];
} while (!lastUpdate.compareAndSet(oldValue, System.currentTimeMillis(),
oldStamp, oldStamp + 1));
}
public long getCount() {
return baseCount.sum();
}
public long getLastUpdateTime() {
return lastUpdate.getReference();
}
}
总结
| 问题 | 描述 | 解决方案 |
|---|---|---|
| ABA 问题 | 值从 A→B→A,CAS 误认为无变化 | 使用版本号(AtomicStampedReference) |
| 循环开销大 | 高竞争下大量线程空转 | 退避策略、LongAdder、分散热点 |
| 单变量限制 | 无法保证多变量原子性 | 对象封装、锁机制、VarHandle |
理解这三大问题并选择合适的解决方案,对于编写正确、高效的无锁并发程序至关重要。在实际开发中,应根据具体场景权衡各种方案的优缺点。
好的,CAS(Compare and Swap)虽然强大,但也存在三个经典的棘手问题。下面我们来详细解析这三大经典问题。
问题一:ABA 问题
什么是 ABA 问题?
ABA 问题是 CAS 操作中最著名、最经典的问题。它的产生过程如下:
问题在于:虽然 CAS 在技术上成功了,但 V 的值在期间已经经历了 A → B → A 的变化,这个"中间状态"被线程 1 完全忽略了。
ABA 问题的危害
用一个现实生活中的例子:
虽然看起来都是"一瓶水",但内容已经发生了变化。
在编程中的具体危害:
解决方案
1. 添加版本号(Version Stamp) 每次修改都递增一个版本号,CAS 时同时检查值和版本号。
Java 中的实现:AtomicStampedReference
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABASolution {
public static void main(String[] args) {
// 初始值为 100,版本号为 0
AtomicStampedReference<Integer> atomicStampedRef =
new AtomicStampedReference<>(100, 0);
int oldValue = 100;
int newValue = 200;
int oldStamp = atomicStampedRef.getStamp(); // 获取当前版本号
// 线程1:尝试CAS,但被挂起
// 线程2:修改值 100 -> 150,版本号 0 -> 1
boolean success2 = atomicStampedRef.compareAndSet(100, 150, 0, 1);
// 线程2:再次修改值 150 -> 100,版本号 1 -> 2
boolean success3 = atomicStampedRef.compareAndSet(150, 100, 1, 2);
// 线程1:恢复执行,尝试CAS
// 虽然值还是100,但版本号已经从0变成2,所以CAS失败!
boolean success1 = atomicStampedRef.compareAndSet(oldValue, newValue, oldStamp, oldStamp + 1);
System.out.println("Thread1 CAS: " + success1); // false
System.out.println("Current value: " + atomicStampedRef.getReference()); // 100
System.out.println("Current stamp: " + atomicStampedRef.getStamp()); // 2
}
}
2. 使用布尔标记
Java 中的 AtomicMarkableReference 用一个布尔值来标记是否被修改过。
import java.util.concurrent.atomic.AtomicMarkableReference;
public class MarkableSolution {
public static void main(String[] args) {
AtomicMarkableReference<Integer> atomicMarkableRef =
new AtomicMarkableReference<>(100, false);
// 类似的CAS操作,同时检查值和标记位
boolean success = atomicMarkableRef.compareAndSet(100, 200, false, true);
}
}
问题二:循环时间长开销大
问题描述
在高并发环境下,当多个线程同时竞争同一个资源时:
具体表现
public class SpinProblem {
private AtomicInteger counter = new AtomicInteger(0);
public void highContentionMethod() {
// 在高并发环境下,这个循环可能执行很多次
while (!counter.compareAndSet(counter.get(), counter.get() + 1)) {
// 空循环,消耗CPU
}
}
}
解决方案
1. 引入退避机制 失败后不是立即重试,而是等待一小段时间:
public class BackoffSolution {
private AtomicInteger counter = new AtomicInteger(0);
public void casWithBackoff() {
int backoffTime = 1; // 初始退避时间
while (true) {
int current = counter.get();
int next = current + 1;
if (counter.compareAndSet(current, next)) {
break; // 成功,退出循环
} else {
// 失败,执行退避
try {
Thread.sleep(backoffTime);
backoffTime = Math.min(backoffTime * 2, 100); // 指数退避,但有上限
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
2. 使用 LongAdder(JDK8+)
在超高并发统计场景下,LongAdder 比 AtomicLong 性能更好:
import java.util.concurrent.atomic.LongAdder;
public class LongAdderSolution {
private LongAdder counter = new LongAdder();
public void increment() {
counter.increment(); // 内部使用分段CAS,减少竞争
}
public long getValue() {
return counter.sum();
}
}
3. 切换为锁机制 当检测到竞争过于激烈时,可以降级使用传统的锁:
public class AdaptiveSolution {
private AtomicInteger casCounter = new AtomicInteger(0);
private final Object lock = new Object();
private int lockCounter = 0;
private int failureCount = 0;
public void adaptiveIncrement() {
if (failureCount > 1000) { // 如果失败次数太多,使用锁
synchronized(lock) {
lockCounter++;
}
} else {
if (!casCounter.compareAndSet(casCounter.get(), casCounter.get() + 1)) {
failureCount++;
// 可能递归调用或其他处理
}
}
}
}
问题三:只能保证一个共享变量的原子操作
问题描述
单个 CAS 指令只能保证一个内存位置的原子性。如果需要同时原子地更新多个相关变量,CAS 就无能为力了。
典型场景
public class MultiVariableProblem {
private AtomicInteger x = new AtomicInteger(0);
private AtomicInteger y = new AtomicInteger(0);
// 错误:这两个CAS操作不是原子的
public void updateBoth(int newX, int newY) {
x.compareAndSet(x.get(), newX); // 第一步
y.compareAndSet(y.get(), newY); // 第二步
// 在两个CAS操作之间,其他线程可能看到不一致的状态
}
}
解决方案
1. 使用 AtomicReference 包装对象 将多个变量封装到一个对象中,然后对整个对象进行 CAS:
import java.util.concurrent.atomic.AtomicReference;
public class MultiVariableSolution {
static class Point {
final int x;
final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
private AtomicReference<Point> pointRef =
new AtomicReference<>(new Point(0, 0));
public void updateBoth(int newX, int newY) {
Point current, next;
do {
current = pointRef.get();
next = new Point(newX, newY);
} while (!pointRef.compareAndSet(current, next));
}
public int getX() {
return pointRef.get().x;
}
public int getY() {
return pointRef.get().y;
}
}
2. 使用锁机制 对于复杂的多变量原子操作,有时使用锁更简单可靠:
public class LockSolution {
private int x = 0;
private int y = 0;
private final Object lock = new Object();
public void updateBoth(int newX, int newY) {
synchronized(lock) {
x = newX;
y = newY;
}
}
}
3. 使用数组 + AtomicReferenceArray 如果需要原子地更新数组中的多个元素:
import java.util.concurrent.atomic.AtomicReferenceArray;
public class ArraySolution {
private AtomicReferenceArray<String> array =
new AtomicReferenceArray<>(10);
// 虽然不能原子更新多个元素,但可以原子更新单个元素
public void updateElement(int index, String newValue) {
array.set(index, newValue);
}
}
总结对比
| 问题 | 描述 | 解决方案 |
|---|---|---|
| ABA 问题 | 值经历了 A→B→A 变化,CAS 无法感知中间状态 | 1. AtomicStampedReference(版本号)2. AtomicMarkableReference(标记位) |
| 循环开销大 | 高竞争下大量线程自旋,消耗 CPU | 1. 退避机制 2. LongAdder 分段3. 竞争激烈时降级为锁 |
| 多变量原子性 | 单个 CAS 只能保证一个变量的原子性 | 1. 使用 AtomicReference 包装对象2. 使用锁 3. 重新设计数据结构 |
实践建议
LongAdder 或退避机制AtomicStampedReference 或直接使用锁AtomicReference理解这三大问题及其解决方案,有助于在实际开发中更好地运用 CAS 技术,在保证线程安全的同时获得最佳性能。
我们来详细探讨乐观锁与悲观锁这两种重要的并发控制策略。
1. 核心思想对比
悲观锁 (Pessimistic Locking) 思想:假设最坏的情况会发生。"我认为只要我访问数据,别人就很可能也会来修改它,所以我要先加锁,确保只有我能操作。"
比喻:
乐观锁 (Optimistic Locking) 思想:假设最好的情况会发生。"我认为别人不太会在我操作的时候修改数据,所以我不加锁,先操作,提交时再检查有没有冲突。"
比喻:
2. 实现机制对比
悲观锁的实现方式
数据库层面:
-- 使用 SELECT ... FOR UPDATE 锁定记录
BEGIN TRANSACTION;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 悲观锁
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
Java 层面:
// 使用 synchronized
public synchronized void transfer(int from, int to, int amount) {
// 转账操作
}
// 使用 ReentrantLock
private final Lock lock = new ReentrantLock();
public void transfer(int from, int to, int amount) {
lock.lock();
try {
// 转账操作
} finally {
lock.unlock();
}
}
乐观锁的实现方式
数据库层面(版本号机制):
-- 添加 version 字段
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 123 AND version = 5; -- 检查版本号
-- 如果受影响行数为0,说明版本号已变化,需要重试
Java 层面(CAS 操作):
// 使用 AtomicInteger
private AtomicInteger balance = new AtomicInteger(1000);
public boolean withdraw(int amount) {
int current, next;
do {
current = balance.get();
if (current < amount) {
return false; // 余额不足
}
next = current - amount;
} while (!balance.compareAndSet(current, next)); // CAS操作
return true;
}
// 使用 AtomicStampedReference 解决ABA问题
private AtomicStampedReference<Integer> account =
new AtomicStampedReference<>(1000, 0);
public boolean transfer(int amount) {
int[] stampHolder = new int[1];
int current, next, currentStamp, nextStamp;
do {
current = account.get(stampHolder);
currentStamp = stampHolder[0];
if (current < amount) return false;
next = current - amount;
nextStamp = currentStamp + 1;
} while (!account.compareAndSet(current, next, currentStamp, nextStamp));
return true;
}
3. 工作流程对比
悲观锁工作流程
1. 申请锁
↓
2. 获取锁成功? → 失败:阻塞等待
↓ 成功
3. 执行业务操作
↓
4. 提交事务,释放锁
乐观锁工作流程
1. 读取数据(包括版本号)
↓
2. 执行业务逻辑(计算新值)
↓
3. 尝试更新(检查版本号)
↓
4. 版本一致? → 否:重试或失败处理
↓ 是
5. 更新成功
4. 优缺点分析
悲观锁的优点
悲观锁的缺点
乐观锁的优点
乐观锁的缺点
5. 适用场景
适合使用悲观锁的场景
具体例子:
适合使用乐观锁的场景
具体例子:
6. 实战示例
库存管理的两种实现
悲观锁实现:
@Service
public class InventoryServicePessimistic {
@Transactional
public boolean deductStock(Long productId, Integer quantity) {
// 悲观锁:查询时锁定记录
Product product = productRepository.findByIdWithLock(productId);
if (product.getStock() >= quantity) {
product.setStock(product.getStock() - quantity);
productRepository.save(product);
return true;
}
return false;
}
}
乐观锁实现:
@Service
public class InventoryServiceOptimistic {
public boolean deductStock(Long productId, Integer quantity) {
int retryCount = 0;
final int MAX_RETRY = 3;
while (retryCount < MAX_RETRY) {
Product product = productRepository.findById(productId);
if (product.getStock() < quantity) {
return false;
}
int updated = productRepository.updateStockWithVersion(
productId, quantity, product.getVersion());
if (updated > 0) {
return true; // 更新成功
}
retryCount++;
// 可选:加入指数退避
try { Thread.sleep(10 * retryCount); } catch (InterruptedException e) { break; }
}
return false; // 重试多次后失败
}
}
7. 总结对比表格
| 特性 | 悲观锁 | 乐观锁 |
|---|---|---|
| 核心思想 | 先加锁,后操作 | 先操作,后检查 |
| 实现方式 | synchronized、ReentrantLock、SELECT FOR UPDATE | CAS、版本号、时间戳 |
| 性能 | 低并发下较好,高并发下差 | 低冲突下极好,高冲突下差 |
| 数据一致性 | 强一致性 | 最终一致性 |
| 死锁 | 可能发生 | 不会发生 |
| 适用场景 | 写多读少、高冲突、长事务 | 读多写少、低冲突、短事务 |
| 开发复杂度 | 简单 | 相对复杂 |
| 扩展性 | 较差 | 较好 |
8. 选择建议
现代趋势:随着分布式系统和微服务的普及,基于版本的乐观锁机制越来越受欢迎,因为它更适合分布式环境,能提供更好的扩展性和性能。