1 概述
CAS即 Compare and Swap ,其是 JDK 提供的非阻塞原子性操作,它通过硬件保证了比较-更新操作的原子性。
或许我们可能会有这样的疑问,假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?答案是否定的,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
2 Unsafe
JDK 的此jar 包中 Unsafe 类提供了 别的原子性操作 Unsafe 类中的方法都是native 方法,它们使用JNI的方式访问本地C++实现库。
//比较对象 obj 中偏移量 offset 的变量的值是否与 expect 相等 若相等 ,使用 update更新 然后返回 true,否则返回 false
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
3 ABA问题
关于 CAS 操作有 典的 ABA 具体如下:假如线程 使用CAS 修改初始值为A的变量X,那么线程1会首先去获取当前变量X的值(为A然后使用 CAS操作尝试修改X值为B,如果使 CAS操作成功了,那么程序运行一定是正确的吗?其实未必,这是因为有可能在线程 获取变 的值 后,在执行 AS 前,线程2使用 CAS 修改了变量X,然后又使用CAS 修改X变量的值为A,所以虽然线程1执行CAS时X的值是A, 但是这时己经不是线程1获取时的A了,这就是 ABA的问题。
ABA 产生是因为变量的状态值产生了环形转换,就是变量的值可以从A到B, 然后再从B到A。如果变量的值只能朝着一个方向转换,比入A到B,B到C,不构成环形,就不会存在问题。 JDK AtomicStampedReference 类给每个变量的状态值都配备了一个时间戳,避免了 ABA 问题 产生。
3.1 ABA问题示例
public class ABATest {
public static void main(String[] args) {
Person p1 = new Person(1, "小明", 1);
Person p2 = new Person(2, "小红", 2);
Person p3 = new Person(3, "小李", 3);
AtomicReference ar = new AtomicReference(p1);
new Thread(() -> {
boolean b = ar.compareAndSet(p1, p2);
if (b) {
p1.setName("小王");
System.out.println("编号1姓名被修改为小王");
}
boolean b1 = ar.compareAndSet(p2, p1);
System.out.println("b1:" + b1);
}).start();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = ar.compareAndSet(p1, p3);
if (b) {
System.out.println("CAS 成功,"+p1.toString());
}
}).start();
}
}
class Person {
private Integer id;
private String name;
private Integer number;
public Person(Integer id, String name, Integer number) {
this.id = id;
this.name = name;
this.number = number;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", number=" + number +
'}';
}
}
输出:
编号1姓名被修改为小王
b1:true
CAS 成功,Person{id=1, name='小王', number=1}
3.2 解决ABA问题示例
public class ResolveABATest {
public static void main(String[] args) {
Person p1 = new Person(1, "小明", 1);
Person p2 = new Person(2, "小红", 2);
Person p3 = new Person(3, "小李", 3);
AtomicStampedReference asr = new AtomicStampedReference(p1, 1);
int stamp = asr.getStamp();
new Thread(() -> {
boolean b = asr.compareAndSet(p1, p2, stamp, stamp + 1);
if (b) {
p1.setName("小王");
}
boolean b1 = asr.compareAndSet(p2, p1, asr.getStamp(), asr.getStamp() + 1);
System.out.println("b1:" + b1);
}).start();
new Thread(() -> {
boolean b = asr.compareAndSet(p1, p3, stamp, stamp + 1);
if (b) {
System.out.println("CAS 成功," + p1.toString());
} else {
System.out.println("CAS 失败!");
}
}).start();
}
}
输出:
b1:true
CAS 失败!