CAS机制
CAS是Compare-And-Swap的缩写,即比较和交换,比较和交换是原子操作,通过CPU指令层面保证了其原子性。CAS通过将预期的值和实际的值进行比较,如果预期的值和实际的值一样,则认为实际值没有发生变化,此时可以将实际值设置为新值。下面通过一段Java代码来演示一下CAS的使用:
java
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class Counter {
private volatile int i = 0;
/**
* Unsafe 是 JDK封装的一个内部使用类,需要使用反射的方式来创建其对象
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
}
void casSetValue(int expected, int newValue) throws NoSuchFieldException, IllegalAccessException {
Unsafe unsafe = getUnsafe();
long offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("i"));
unsafe.compareAndSwapInt(this, offset, expected, newValue);
}
int getValue() {
return i;
}
}
public class CasDemo01 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Counter counter = new Counter();
// 这里预期值是2,但实际值是0,新的值会设置失败
counter.casSetValue(2, 1);
// 输出的结果为: 0
System.out.println(counter.getValue());
// 这里预期值是0,实际值也是0,新的值设置成功
counter.casSetValue(0, 2);
// 输出的结果为: 2
System.out.println(counter.getValue());
}
}
上面演示了Java代码中如何使用CAS机制,Java提供了Unsafe类,其中有提供三个实现CAS机制的核心方法,分别对不同类型的变量进行修改。
java
//用于整型变量的原子操作
boolean compareAndSwapInt(Object o, long offset, int expected, int x)
//用于长整型变量的原子操作
boolean compareAndSwapLong(Object o, long offset, long expected, long x)
//用于对象引用的原子操作,不涉及对象的属性
boolean compareAndSwapObject(Object o, long offset, Object expected, Object x)
这些方法都调用了Java本地方法,由JVM提供底层实现。现代的CPU架构中,这些操作通常会被编译为特定的CPU指令。在x86架构中,对应的是CMPXCHG,它可以原子性的完成比较和交换操作,确保在多线程环境下的线程安全性。
这种硬件级别的支持使得CAS非常高效:
- 由CAS实现的同步,避免了synchronized锁带来的上下文切换
- 实现了真正的无锁编程
- 在竞争不激烈的情况下,性能显著优于传统的同步机制(因为一旦竞争过于激烈,会存在CAS一直失败的情况,反而性能有限)。
ABA问题
如果有一个整型值X,一开始实际值是1,然后被修改为2,最后又恢复成1,这个时候我们使用compareAndSwapInt(Object o, long offset, int 1, int 3)来修改这个X的时候,虽然修改可以成功,看起来没什么问题,但是CAS无法感知这中间的变化,可能导致业务逻辑问题。
要解决这个问题,需要为要修改的值提供一个版本号。下面是Java的concurrent包中的一个实现:
java
import java.util.concurrent.atomic.AtomicStampedReference;
public class AbaProblem {
public static void main(String[] args) {
// 初始化版本号号是0
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(1, 0);
Integer stamp = ref.getStamp();
// 输出 0
System.out.println("版本号是:" + stamp);
ref.compareAndSet(1, 3, stamp, stamp + 1);
// 输出 1
System.out.println("新的版本号是:" + ref.getStamp());
}
}
查看源码发现,AtomicStampedReference定义了一个私有内部类Pair,使用Pair存储值和版本号。在 ref.compareAndSet中创建一个新的Pair对象,并通过compareAndSwapObject()方法更新AtomicStampedReference中对Pair对象的引用,可以起到同时原子更新两个变量的效果(值和版本号)。
java
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
ref.compareAndSet的源码如下
java
// Pair.of 创建新的Pair对象
// casPair方法,类似上面的compareAndSwapObject方法,原子化更新对象的引用
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}