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,否则返回 falsepublic 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;}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", number=" + number +'}';}}
输出:
编号1姓名被修改为小王b1:trueCAS 成功,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:trueCAS 失败!
