CAS(Compare-And-Swap)是一种重要的无锁的原子算法,它在多线程环境下使用硬件原语来同步对共享数据的访问。CAS操作通常由现代CPU直接支持,并在操作系统和编程语言的并发库中广泛应用。
CAS的基本概念
CAS操作包含三个操作数:
- 内存位置(内存中的特定地址)
- 预期原值(预期读取到的值)
- 新值(需要写入的值)
算法流程:
- 从内存位置读取当前值。
- 比较当前值与预期原值是否相等。
- 如果相等,将新值写入内存位置。
- 如果不相等,不做任何操作。
CAS的Java实现
在Java中,java.util.concurrent.atomic
包提供了一系列的原子类来支持无锁的线程安全编程。下面是AtomicInteger
中的incrementAndGet
方法的简化版实现,这个方法通过CAS原子性地递增变量的值:
java
public final class AtomicInteger extends Number implements java.io.Serializable {
// Unsafe mechanics
private static final sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
//...其他方法和构造函数
public final int incrementAndGet() {
int current;
int next;
do {
current = unsafe.getIntVolatile(this, valueOffset); // 读取当前值
next = current + 1; // 计算新值
} while (!unsafe.compareAndSwapInt(this, valueOffset, current, next)); // CAS操作
return next;
}
//...其他方法和构造函数
}
在这个例子中,incrementAndGet
使用CAS循环来更新value
字段。如果在尝试更新时其他线程已经更改了value
,循环会持续不断地尝试,直到成功为止。
CAS的细节和问题
尽管CAS是非阻塞的,但它并不完美。在实际应用中,有以下几个注意点:
-
ABA问题
ABA问题发生在一个变量从A变化到B,然后又变回A,CAS将无法感知中间发生的变化。为解决这一问题,可以使用带版本号的CAS,如
AtomicStampedReference
类。 -
循环时间长开销大
当多个线程尝试对同一个变量执行CAS时,可能会引发激烈的竞争。如果一个线程一直无法成功更新变量,会导致高CPU负载。
-
只能保证一个共享变量的原子操作
CAS仅能对单一的共享变量进行原子操作。当涉及到多个共享变量时,可以使用锁或者
java.util.concurrent
包下的原子类,如AtomicReference
。
CAS的代码示例
下面是一个简单的基于CAS的计数器实现:
java
import java.util.concurrent.atomic.AtomicInteger;
public class CASCounter {
private AtomicInteger count = new AtomicInteger(0);
public int increment() {
int oldValue, newValue;
do {
oldValue = count.get(); // 获取当前的值
newValue = oldValue + 1; // 期望递增后的值
} while (!count.compareAndSet(oldValue, newValue)); // CAS操作
return newValue;
}
public int getValue() {
return count.get();
}
}
在这个例子中,increment()
方法不断地尝试CAS操作,直到成功。compareAndSet
是一个原子操作,如果当前值与预期值oldValue
相同,就把它设置为newValue
。
总结
CAS是并发编程中的关键技术,特别是在编写无锁数据结构时。它能够减少锁的使用,从而减少线程之间的阻塞和上下文切换。然而,CAS不是完全没有代价的,它可能导致高CPU负载,并需要仔细处理ABA问题。通过Java中的java.util.concurrent.atomic
包,可以轻松地实现基于CAS的线程安全操作。了解CAS的工作原理及其局限性对编写高效、可扩展的并发应用至关重要。