一、CAS
CAS的全称是Compare-And-Swap,它是一条CPU并发原语
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的
CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中各个方法,调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令,这个一种完全依赖于硬件的功能,通过它实现了原子操作。CAS是一条CPU原子指令,不会造成所谓的数据不一致问题。
二、Unsafe类
Unsafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据,Unsafe类其内部方法操作可以像C的指针一样直接操作内存
Unsafe实现CAS的方法,比较当前工作内存中的值和主内存中的值,如果相同则执行规定的操作,返回true,否则返回false
// Unsafe.java
public final native boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
public final native boolean compareAndSwapLong(Object o, long offset,
long expected,
long x);
三、CAS缺点
1.只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可以用锁来保证原子性
2.CAS会导致ABA问题
CAS 操作在更新变量前,会先检查当前内存中的值是否与预期的旧值相等,如果相等才会进行更新。ABA 问题指的是:
假设有一个共享变量初始值为 A,线程 T1 准备使用 CAS 将其修改为 Z。在 T1 执行 CAS 之前,线程 T2 介入,将该变量的值从 A 改成了 B,随后又将其从 B 改回了 A。当 T1 恢复执行 CAS 时,发现内存中的值依然是 A,于是认为该变量从未被修改过,从而成功执行了更新操作。
3.ABA 问题带来的实际隐患
虽然在很多情况下,变量经历 A -> B -> A 的变化对最终结果没有影响,但在某些特殊场景下会引发严重的逻辑错误。最典型的例子是银行并发扣款:
假设账户余额为 100 元,由于网络卡顿,用户连续点击了两次取款 50 元,系统生成了线程 1 和线程 2:
线程 1 获取余额 100 元,期望更新为 50 元;线程 2 同样获取余额 100 元,期望更新为 50 元。
线程 1 成功执行扣款,余额变为 50 元,此时线程 2 处于阻塞等待状态。
此时,恰好有朋友给该账户转账 50 元(触发线程 3),CPU 调度线程 3 抢先执行,账户余额重新变回 100 元。
线程 2 恢复执行,发现当前余额依然是 100 元,与自己之前读取的期望值相同,于是 CAS 操作成功,再次扣款 50 元,余额变为 50 元。
错误结果: 正确的余额应该是 100 元(100 - 50 + 50),但由于 ABA 问题,实际余额变成了 50 元(100 - 50 + 50 - 50)。
四、AtomicInteger类对CAS的使用
代码:
使用AtomicInteger实现原子操作
public class VolatileTest {
public static void main(String[] args) throws InterruptedException {
ShareData shareData = new ShareData();
for (int i = 0; i < 20; i++){
new Thread(()->{
for (int j = 0; j < 1000; j++){
shareData.numberVolatile++;
shareData.atomicInteger.getAndIncrement();
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println("numberVolatile:"+shareData.numberVolatile);
System.out.println("atomicInteger:"+shareData.atomicInteger);
}
}
class ShareData {
public volatile int numberVolatile = 0;
public AtomicInteger atomicInteger = new AtomicInteger(0);
}
结果:
numberVolatile:19919
atomicInteger:20000
分析:
AtomicInteger的value变量使用volatile保证线程间的可见性
// AtomicInteger.java
private static final Unsafe unsafe = Unsafe.getUnsafe();
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
AtomicInteger对象调用Unsafe方法的getAndAddInt方法,getAndAddInt方法循环调用compareAndSwapInt方法
compareAndSwapInt判断内存某个位置的值是否为预期值,如果是则更改为新的值,返回true,否则返回false
// Unsafe.java
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;
}
五、AtomicStampedReference解决ABA问题
AtomicStampedReference为时间戳原子引用
public class ABADemo {
private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
System.out.println("============以下是ABA问题的产生============");
new Thread(()->{
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019));
System.out.println(atomicReference.get());
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("============以下是ABA问题的解决============");
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "t 第1次版本号:"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "t 第2次版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "t 第3次版本号:"+atomicStampedReference.getStamp());
}, "t3").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "t 第1次版本号:"+stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "t result:"+result);
System.out.println(Thread.currentThread().getName() + "t 当前实际最新值:"+atomicStampedReference.getReference());
}, "t4").start();
}
}
结果:
============以下是ABA问题的产生============
true
2019
============以下是ABA问题的解决============
t3t 第1次版本号:1
t4t 第1次版本号:1
t3t 第2次版本号:2
t3t 第3次版本号:3
t4t result:false
t4t 当前实际最新值:100