1.概念:CAS的全稱爲Compare-And-Swap,是一條CPU併發原語。
它的功能是判斷內存某個位置的值是否爲預期值,如果是則更改爲新的值,這個過程是原子的。
示例:
CAS併發原語體現在JAVA的sun.misc.Unsafe類中的各個方法。調用 UnSafe類中的CAS方法,JVM會幫我們實現出CAS彙編範疇。這是一種完全依賴於硬件的操作。
2.CAS底層思想
觀察AtomicInteger中的getAndIncrement()方法,
變量valueOffset表示在內存中偏移地址,value被volatile關鍵字修飾,保證了可見性
這個方法就是藉助UnSafe類中的getAndAddInt()方法實現的。
再進入UnSafe類內部,找到getAndAddInt()方法,
解釋:
3.CAS的缺點:
1)某些情況下可能會導致循環時間過長,致使cpu開銷過大;
2)只能保證一個共享變量的原子操作;
3)ABA問題
何爲ABA問題??
解釋:⼀箇舊值A變爲了成B,然後再變成A,剛好在做CAS時檢查發現舊值並沒有變化依然爲A,但是實際上的確發生了變化。
例如,一個線程1從主內存位置V處找到A,並拷貝一份A回自己的工作內存中,此時另一個線程2也從主內存位置V處取到A,也拷貝了一份A回自己的工作內存中,線程2進行了一些操作將值變成了B,然後線程2又將主內存V位置處的數據變成A,這時候線程1進行CAS操作時發現內存中的值仍然是A,然後線程1操作成功。也就是說主內存A中的值並沒有發生根本的改變。
那麼如何解決ABA問題呢?
使用AtomicStampedReference(時間戳原子引用),類似於數據庫中常用的樂觀鎖的方式,引入一個版本號來解決。
驗證代碼:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
//普通原子引用
private static AtomicReference<Integer> atomicInteger = new AtomicReference<>(100);
private static AtomicStampedReference<Integer> atomicStampedReference =
new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("=========以下是ABA的產生================");
new Thread(()->{
boolean flag1 = atomicInteger.compareAndSet(100, 101);
boolean flag2 = atomicInteger.compareAndSet(101, 100);
System.out.println(flag1+",current data:"+atomicInteger.get());
System.out.println(flag2+",current data:"+atomicInteger.get());
},"t1").start();
new Thread(()->{
try{
//保證上面的t1線程,保證t1線程完成了一次CAS操作
TimeUnit.SECONDS.sleep(1);
System.out.println(atomicInteger.compareAndSet(100,2019)+"\t"+atomicInteger.get());
}catch (InterruptedException e){
e.printStackTrace();
}
},"t2").start();
//暫停2秒
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() +","+stamp+"\t第1次版本號:"+stamp);
//暫停1秒鐘t3線程
try {
TimeUnit.SECONDS.sleep(1);
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),
atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() +","+stamp+"\t第2次版本號:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),
atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() +","+stamp+"\t第3次版本號:"+atomicStampedReference.getStamp());
}catch (InterruptedException e){
e.printStackTrace();
}
},"t3").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() +","+stamp+"\t首次版本號:"+stamp);
//暫停3秒鐘t4線程,保證t3線程完成一次CAS操作
try {
TimeUnit.SECONDS.sleep(3);
boolean res = atomicStampedReference.compareAndSet(100,2019,
stamp,stamp+1);
System.out.println(Thread.currentThread().getName() +"\t修改成功否:"+res
+"\t當前最新實際版本號:"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t當前實際最新值:"+atomicStampedReference.getReference());
}catch (InterruptedException e){
e.printStackTrace();
}
},"t4").start();
}
}
運行結果:
此時,已經很好地解決了ABA問題在實際開發中給我們帶來的不便。