Java CAS

CAS 的全称为 Compare-And-Swap(对比交换)。是一条 CPU 的原子指令,其作用是让 CPU 先进行比较两个值是否相等,然后原子地更新某个位置的值。其实现方式是基于硬件平台的汇编指令,就是说 CAS 是靠硬件实现的,JVM 只是封装了汇编调用,那些以 Atomic 开头的类便是使用了这些封装后的接口。

CAS 操作包含三个操作数 — 内存值、预期值和新值。执行 CAS 操作的时候,将内存值与预期值比较,只有在两者相等时才会对内存中的值进行修改,否则不做任何操作。

CAS 是在保证性能的同时提供并发场景下的线程安全性。在 Java 中 CAS 实现位于 sun.misc.Unsafe 类中,CAS 常用的有以下几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, newValue));
return v;
}

public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}

public native boolean compareAndSwapInt(Object obj, long offset, int expectedValue, int newValue);

public native boolean compareAndSwapObject(Object obj, long offset, Object expectedValue, Object newValue);
  • obj: 目标操作对象
  • offset: 目标操作数内存偏移地址
  • expectedValue: 期望值
  • newValue: 更新值

自旋锁是利用 CAS 而设计的一种应用层面的锁。如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 0代表锁释放,1代表锁被占用
int lock = 0;
while(true){
if(lock==0){
int lock_ ;
if(U.compareAndSwapInt(this,lock_,0,1)){
// 获取锁后的逻辑处理

// 最后释放锁
lock = 0;
break;
}
}
}

实现线程安全的方式可以分为三类:

  1. 互斥同步: synchronized 和 ReentrantLock;
  2. 非阻塞同步: CAS、AtomicInteger 等原子类;
  3. 无同步方案: 栈封闭、Thread Local、只读变量;

CAS 的问题

CAS 方式为乐观锁,synchronized 为悲观锁。使用 CAS 解决并发问题通常情况下性能更优。但也存在如下几个问题:

1. ABA 问题:

因为 CAS 需要在操作值的时候,检查值有没有发生变化,没有发生变化则更新,但是如果一个值原来是 A,变成了 B,又变成了 A,那么使用 CAS 进行检查时则会发现它的值没有发生变化,但是实际上却变化了。

ABA 问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么 A-B-A 就会变成 1A - 2B-3A。

也可以使用 JDK 的 atomic 包里提供的 AtomicStampedReference 来解决。
通过检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2. 循环时间长开销大:

3. 只能保证一个共享变量的原子操作:

JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,就可以把多个变量放在同一个对象里来进行 CAS 操作。

UnSafe 类

UnSafe 类功能

参考

[1] Unsafe - openjdk
[2] JUC 原子类: CAS, Unsafe 和原子类详解
[3] Java 魔法类:Unsafe 应用解析