一、什么是CAS
CAS(Compare and Swap)是处理并发问题的常用技术之一,它解决了线程安全和数据一致性问题。
CAS 是一种乐观锁机制,它是通过底层硬件提供的原子操作指令实现的,能够保证在多线程同时访问同一变量时,只有一个线程可以修改该变量的值。
CAS操作通常基于三个参数:内存位置V、期望值A和新值B。
CAS操作的执行过程如下:
1.首先,读取内存位置V的当前值,并将其与期望值A进行比较。
2.如果两者相等,则将内存位置V的值修改为新值B,并返回true,表示修改成功。
3.如果两者不相等,则不作修改,并返回false,表示修改失败。
简单来说就是:如果当前内存位置V的值等于期望值A,则将其修改为新值B;否则不做任何操作。
二、Java中的CAS
在 Java 中,CAS 操作通常是通过 sun.misc.Unsafe 类来提供支持的。Unsafe 类提供了一组底层操作,可以直接访问内存并执行一些原子性操作。如下是 Unsafe 中的三个 native 关键字修饰的CAS方法,Java 中的 CAS 操作都是通过这三个方法实现的。
public final class Unsafe {
......
// Object 的 CAS操作
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
// int 的 CAS操作
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
// long 的 CAS操作
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
// 自旋调用 CAS操作
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
// 自旋调用 CAS操作
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
// 自旋调用 CAS操作
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
public final long getAndSetLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
// 自旋调用 CAS操作
} while(!this.compareAndSwapLong(var1, var2, var6, var4));
return var6;
}
public final Object getAndSetObject(Object var1, long var2, Object var4) {
Object var5;
do {
var5 = this.getObjectVolatile(var1, var2);
// 自旋调用 CAS操作
} while(!this.compareAndSwapObject(var1, var2, var5, var4));
return var5;
}
......
}
例如 AtomicInteger 源码中使用 CAS 的部分源码附带说明如下:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 使用 Unsafe.compareAndSwapInt 来更新值
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获取 value 属性在对象中的偏移量
private static final long valueOffset;
static {
try {
// 获取 value 属性在对象中的偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
// 使用 volatile 修饰 value 属性,保证线程之间的可见性
private volatile int value;
......
// 自增并返回自增前的值
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 自增并返回自增后的值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
......
}
三、注意事项
需要注意的是,虽然CAS操作具有很好的互斥性和原子性,但也存在一些需要注意的问题:
- 自旋次数过多会影响性能:当多个线程访问同一变量时,如果修改失败,CAS操作会进行自旋等待,以期望其他线程修改完成后再次尝试。如果自旋次数过多,会浪费大量的CPU资源。
- 受限于底层硬件平台:由于CAS操作是基于底层硬件平台提供的支持,因此受限于硬件平台的类型和性能,不同的硬件平台可能对CAS操作的具体实现略有差别。
- 无法避免ABA问题:当变量从A -> B -> A后,如果期望值A没有变化,CAS操作仍然会成功,但实际上变量的值已经发生了变化。如果你想了解更多关于ABA问题,请查看我的另一篇文章【 还不知道ABA问题?大厂面试Java必提的问题 】