Java 中的synchronized
是一種悲觀鎖逗噩,悲觀鎖始終假設(shè)會(huì)發(fā)生并發(fā)沖突蝎亚,因此會(huì)阻止一切可能違反數(shù)據(jù)完整性的操作狠怨。而 CAS 是一種樂觀鎖
CAS 全稱是 Compare and Swap却妨。樂觀鎖假設(shè)不會(huì)發(fā)生并發(fā)沖突哑梳,因此只在提交的時(shí)候檢查是否違反數(shù)據(jù)完整性耻陕,如果提交失敗則會(huì)進(jìn)行重試拙徽。
CAS 支持原子更新操作,適用于計(jì)數(shù)器诗宣,序列發(fā)生器等場(chǎng)景膘怕。CAS 操作失敗時(shí)由開發(fā)者決定是繼續(xù)嘗試,還是執(zhí)行別的操作召庞。
CAS 的思想:
CAS機(jī)制當(dāng)中使用了3個(gè)基本操作數(shù):內(nèi)存地址V岛心,舊的預(yù)期值A(chǔ),要修改的新值B篮灼。
更新一個(gè)變量的時(shí)候忘古,只有當(dāng)變量的預(yù)期值A(chǔ)和內(nèi)存地址V當(dāng)中的實(shí)際值相同時(shí),才會(huì)將內(nèi)存地址V對(duì)應(yīng)的值修改為B诅诱。
public class CASCase {
public volatile int value;
public void add(){
value++;
}
}
使用javap
查看編譯后的add()
方法中的value++
的字節(jié)碼如下:
2: getfield #2 // Field value:I
5: iconst_1
6: iadd
7: putfield #2 // Field value:I
可以看到value++
被分成了三步髓堪,雖然value
變量使用了volatile
修飾,對(duì)value++
操作禁止了指令重排序,但是value++
不是原子性操作干旁,在多線程并發(fā)下還是會(huì)產(chǎn)生線程安全問題驶沼。一種解決的辦法是給add()
方法加上synchronized
關(guān)鍵字,但是synchronized
屬于悲觀鎖争群,是否能夠進(jìn)一步提升性能呢商乎?其實(shí)還可以使用AtomicInteger
來滿足我們的要求
public class CASCase {
public AtomicInteger value;
public void add(){
value.getAndIncrement();
}
}
在getAndIncrement()
方法底層調(diào)用的是Unsafe
類的compareAndSetInt
方法,用到的就是 CAS 的思想祭阀。
CAS 多數(shù)情況下對(duì)開發(fā)者來說是透明的。J.U.C 的 atomic 包提供了常用了原子性數(shù)據(jù)類型以及引用鲜戒、
數(shù)組等相關(guān)原子類型和更新操作工具专控,是很多線程安全程序的首選。Unsafe 類雖然提供 CAS 服務(wù)遏餐,但是因?yàn)槟軌虿倏v任意內(nèi)存地址讀寫而有隱患伦腐。在 Java9 之后,可以使用 Variable Handle API 來替代 Unsafe失都。
CAS 雖然很高效柏蘑,但是也有缺點(diǎn):
- CAS 若循環(huán)時(shí)間長,則開銷很大粹庞。我們可以看到在
Unsafe
類的getAndAddInt()
執(zhí)行時(shí)咳焚,如果 CAS 失敗,會(huì)一直循環(huán)嘗試進(jìn)行 CAS 賦值庞溜,如果長時(shí)間都在循環(huán)革半,會(huì)給 CPU 帶來很大開銷 - CAS 只能保證一個(gè)共享變量的原子操作,但無法保證對(duì)多個(gè)共享變量的原子性操作
- ABA 問題流码。如果一個(gè)變量在進(jìn)行 CAS 操作第一次讀取時(shí)是 A又官,中間被其他操作改成了 B,又改回了 A漫试,等到 CAS 操作第二次讀取的時(shí)候也是 A六敬,CAS 就會(huì)誤認(rèn)為這個(gè)變量從來沒有被改變過。在一些場(chǎng)景中驾荣,ABA 問題可能會(huì)影響程序的并發(fā)正確性外构。為了解決這個(gè)問題,J.U.C 提供了
AtomicStampedReference
秘车,可以通過變量值的版本來保證 CAS 的正確性典勇。不過在 ABA 問題下,可能用傳統(tǒng)的 加鎖互斥的方法更加高效叮趴。
原文發(fā)表于:https://zhangxiann.com/archives/cas
歡迎關(guān)注我的博客:zhangxiann.com