什么是CAS
簡單的說,CAS
就是compare and swap
,翻譯成中文就是比較與交換.在java多線程中,我們可以使用鎖,synchronized等來保證多線程下共享數(shù)據(jù)的安全,這些方式都算是悲觀鎖
,而CAS
算是樂觀鎖
.而所謂的樂觀鎖和悲觀鎖只是我們人為的一種劃分.悲觀鎖認為每次并發(fā)都認為別人會修改共享數(shù)據(jù),使用鎖和同步代碼塊等方式使得一次只能有一個線程去操作共享數(shù)據(jù).樂觀鎖則認為每次并發(fā)都不會有其他人修改共享數(shù)據(jù),只是在更新的時候才去判斷數(shù)據(jù)是否被修改過.
CAS
是一種無鎖算法,它的核心思想就是比較和交換.CAS涉及三個操作數(shù):
- 需要讀寫的內存值V(就是內存中現(xiàn)在的值)
- 進行比較的值A(之前從內存中讀取的值)
- 擬定寫入的值B(更新的新值)
當且僅當V的值和A的值一樣時,CAS才會使用原子的方式使用新值B來更新V的值(這里面的比較和設置新值是一個原子操作).如果上面的相等條件不成立,那么會進行一個自旋操作,即重新嘗試.
優(yōu)缺點
優(yōu)點
- 在并發(fā)量不是特別大的時候,它的性能要比使用synchronized和鎖的效率要高.
缺點
-
循環(huán)時間長開銷大
:在高并發(fā)的情況下,CAS失敗次數(shù)增加,導致嘗試次數(shù)增加. -
只能保證一個共享變量的原子操作
:當使用一個變量時,我們可以循環(huán)使用CAS的方式保證原子操作.但是如果是多個變量時,這個時候并不能保證還是原子操作. -
ABA問題
:如果內存的初讀值為A,然后另外一個線程將其值改成B,然后又改回A.那么這個時候使用CAS判斷是成功的.實際上真正的值已經經過了一次修改.所以在使用時,也需要考慮清楚ABA
問題會不會影響業(yè)務數(shù)據(jù).
java中是實現(xiàn)
在jdk中主要提供了四類CAS實現(xiàn),分別為:
- 基本類型
- 數(shù)組類型
-
AtomicIntegerArray
原子更新整型數(shù)組 -
AtomicLongArray
原子更新整型數(shù)組 -
AtomicReferenceArray
原子更新引用類型數(shù)組
-
- 引用類型
-
AtomicReference
原子更新引用類型 -
AtomicReferenceFieldUpdater
原子更新引用類型字段類型 -
AtomicMarkableReference
原子更新帶有標記的引用類型
-
- 字段類型
-
AtomicIntegerFieldUpdater
原子更新整型的字段的更新器 -
AtomicLongFieldUpdater
原子更新長整型的字段的更新器 -
AtomicStampedReference
原子更新帶有版本號的引用類型
-
java解決ABA問題
public class App5 {
private static AtomicInteger i = new AtomicInteger(1);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
String name = Thread.currentThread().getName();
//獲取值
int old = App5.i.get();
System.out.println(name+" ==> i ="+ old);
// 休眠1秒
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新值
boolean success = i.compareAndSet(old, 100);
if (success){
System.out.println(name+" CAS更新操作成功 ==> " +i.get());
}else {
System.out.println(name+" CAS更新操作失敗 ==> " +i.get());
}
}, "t1");
Thread t2 = new Thread(()->{
String name = Thread.currentThread().getName();
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改成2
i.set(2);
System.out.println(name+" ==> i = "+i.get());
//修改成1
i.set(1);
System.out.println(name+" ==> i = "+i.get());
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
最后打印結果如下:
t1 ==> i =1
t2 ==> i = 2
t2 ==> i = 1
t1 CAS更新操作成功 ==> 100
上面是ABA問題的示例代碼.當線程t1在準備更新值時(此時值為1),因為線程睡眠t1暫停執(zhí)行后面的代碼.t2開始執(zhí)行,t2先將值改為2,然后t2又將值改回1.當線程t1睡眠結束,使用compareAndSet
更新值為100時,發(fā)現(xiàn)預期值沒有發(fā)生改變,認為沒有其他線程修改過該數(shù)據(jù),那么這就出現(xiàn)了ABA的問題.
在jdk中提供了AtomicMarkableReference
和AtomicStampedReference
來解決這個問題.一個是通過boolean來判斷值是否改變過,另一個通過一個整型的版本號來判斷是否改變過,這樣更新的時候不僅僅只看值是否發(fā)生過改變,還需要判斷更新標記或者版本號是否發(fā)生過改變來判斷值是否有過改變.
底層實現(xiàn)
通過查看源碼,發(fā)現(xiàn)底層最后調用的都是Unsafe類提供的方法實現(xiàn).該類主要提供了一些執(zhí)行級別低,不安全的操作.例如可以直接訪問JVM以外的內存,可以直接操作內存空間等.歸根結底它就是通過一條CPU原子指令(cmpxchg指令)來實現(xiàn)的,該指令就是CAS的核心思想.具體Unsafe提供哪些功能,可以參考下面鏈接Java魔法類:Unsafe應用解析