1. 什么是 CAS?
1.1 悲觀鎖與樂(lè)觀鎖
悲觀鎖
的原理是每次實(shí)現(xiàn)數(shù)據(jù)庫(kù)的增刪改的時(shí)候都進(jìn)?阻塞,防?數(shù)據(jù)發(fā)?臟讀。
樂(lè)觀鎖
的原理是在數(shù)據(jù)庫(kù)更新的時(shí)候,??個(gè) version 字段來(lái)記錄版本號(hào),然后通過(guò)?較是不是??要修改的版本號(hào)再進(jìn)?修改。這其中就引出了?種?較交換的思路來(lái)實(shí)現(xiàn)數(shù)據(jù)的?致性,事實(shí)上,CAS 也是基于這樣的原理。
1.2 CAS 是什么?
CAS
是指 Compare And Swap,比較并交換
,是一種無(wú)鎖原子算法,在不使用鎖(沒(méi)有線程被阻塞)的情況下實(shí)現(xiàn)多線程之間的變量同步。
JAVA 底層是 C++ 實(shí)現(xiàn),映射到操作系統(tǒng)就是一條 cmpxchg
硬件匯編指令(保證原子性),其作用就是讓 CPU 將內(nèi)存值更新為新值,但是有個(gè)條件,內(nèi)存值必須與期望值相同。并且 CAS
操作無(wú)需用戶態(tài)與內(nèi)核態(tài)切換,直接在用戶態(tài)對(duì)內(nèi)存進(jìn)行讀寫(xiě)操作(意味著不會(huì)阻塞/線程上下文切換)。
java.util.concurrent.atomic 包中的原子類(lèi)就是通過(guò) CAS
來(lái)實(shí)現(xiàn)了樂(lè)觀鎖。
CAS
算法涉及到三個(gè)操作數(shù):
-
需要更新的內(nèi)存變量值 V(volatile);
-
上一次從內(nèi)存中讀取,進(jìn)行比較的預(yù)期原值 E(except);
-
要寫(xiě)入的新值 N(new)。
當(dāng)且僅當(dāng) V 的值等于 E 時(shí),CAS
通過(guò)原子方式用新值 N 來(lái)更新 V 的值(“比較 + 更新” 整體是一個(gè)原子操作),否則不會(huì)執(zhí)行任何操作,這就是一次 CAS
的操作。一般情況下,“更新” 是一個(gè)不斷重試的操作。
簡(jiǎn)單說(shuō),CAS 需要你額外給出一個(gè)期望值,也就是你認(rèn)為這個(gè)變量現(xiàn)在應(yīng)該是什么樣子的,如果變量不是你想象的那樣,說(shuō)明它已經(jīng)被別人修改過(guò)了,你只需要重新讀取,設(shè)置新期望值,再次嘗試修改就好了。
2. CAS 核心源碼
java.util.concurrent.atomic 包中的原子類(lèi)就是通過(guò) CAS
思想來(lái)實(shí)現(xiàn),CAS 思想實(shí)現(xiàn)靠的是 Unsafe
類(lèi)。以 AtomicInteger 為例:
Unsafe
類(lèi)是 CAS 的核心類(lèi),由于 Java 方法無(wú)法直接訪問(wèn)底層系統(tǒng),需要通過(guò)本地(native)方法來(lái)訪問(wèn),Unsafe 相當(dāng)于一個(gè)后門(mén),基于該類(lèi)可以直接操作特定內(nèi)存的數(shù)據(jù)。Unsafe 類(lèi)存在于 sun.misc 包中,其內(nèi)部方法操作可以像C的指針一樣直接操作內(nèi)存。
valueOffset
表示當(dāng)前類(lèi)對(duì)象中使用變量的偏移量,Unsafe 就是根據(jù)內(nèi)存偏移地址獲取數(shù)據(jù)的。
value
要修改的值,用 volatile 修飾,保證了多線程之間的內(nèi)存可見(jiàn)性。
一起看一下 AtomicInteger.getAndIncrement() 方法是怎么替換內(nèi)容的:
public final int getAndIncrement() {
// 拿到當(dāng)前對(duì)象和當(dāng)前對(duì)象的地址,然后加一。
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// var1 就是當(dāng)前對(duì)象,var2 就是當(dāng)前對(duì)象的內(nèi)存地址,var4 就是要加的數(shù)
public final int getAndAddInt(Object var1, long var2, int var4) {
// 舊的預(yù)期值
int var5;
do {
// 獲得當(dāng)前 var2 地址中的值,可以理解為從當(dāng)前主物理內(nèi)存中拷貝一份到自己的本地內(nèi)存
var5 = this.getIntVolatile(var1, var2);
}
// 如果var1 = var5,執(zhí)行var5 + var4,執(zhí)行成功返回 true,跳出 while 循環(huán),執(zhí)行失敗返回false,自旋
while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *)index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
} UNSAFE_END
首先獲取 var5
舊的預(yù)期值,然后調(diào)用底層代碼 Unsafe_CompareAndSetInt
。下面看 Unsafe_CompareAndSetInt 怎么做的:
第一步通過(guò) JNIHandles::resolve() 獲取 obj 在內(nèi)存中 OOP 實(shí)例;
第二步根據(jù)成員變量 value 反射后計(jì)算出的內(nèi)存偏移值 offset 去內(nèi)存中取指針 addr;
最后通過(guò) Atomic::cmpxchg(x, addr, e) 實(shí)現(xiàn) CAS。
3. CAS 實(shí)現(xiàn)原子操作的三大問(wèn)題
3.1 ABA 問(wèn)題
并發(fā)環(huán)境下,假設(shè)初始條件是 A,去修改數(shù)據(jù)時(shí),發(fā)現(xiàn)是 A 就會(huì)執(zhí)行修改。但是看到的雖然是 A,中間可能發(fā)生了 A 變 B,B 又變回 A 的情況。此 A 已經(jīng)非彼 A,數(shù)據(jù)即使成功修改,也可能有問(wèn)題。
怎么解決 ABA 問(wèn)題?
加版本號(hào)。
每次修改變量,都在這個(gè)變量的版本號(hào)上加1,這樣發(fā)生 A->B->A 時(shí),雖然 A 的值沒(méi)變,但是它的版本號(hào)已經(jīng)變了,再判斷版本號(hào)就會(huì)發(fā)現(xiàn)此時(shí)的 A 已經(jīng)被改過(guò)了。參考樂(lè)觀鎖的版本號(hào),這種做法可以給數(shù)據(jù)帶上了一種時(shí)效性的檢驗(yàn)。
Java 提供了 AtomicStampReference 類(lèi),它的 compareAndSet 方法首先檢查當(dāng)前的對(duì)象引用值是否等于預(yù)期引用,并且當(dāng)前版本號(hào)(Stamp)標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將引用值和版本號(hào)標(biāo)志的值更新為給定的更新值。
3.2 循環(huán)性能開(kāi)銷(xiāo)
自旋 CAS,如果一直循環(huán)執(zhí)行,一直不成功,會(huì)給 CPU 帶來(lái)非常大的執(zhí)行開(kāi)銷(xiāo)。
怎么解決循環(huán)性能開(kāi)銷(xiāo)問(wèn)題?
在 Java 中,很多使用自旋 CAS 的地方,會(huì)有一個(gè)自旋次數(shù)的限制,超過(guò)一定次數(shù),就停止自旋。
3.3 只能保證一個(gè)變量的原子操作
CAS 保證的是對(duì)一個(gè)變量執(zhí)行操作的原子性,如果對(duì)多個(gè)變量操作時(shí),CAS 目前無(wú)法直接保證操作的原子性的。
怎么解決只能保證一個(gè)變量的原子操作問(wèn)題?
- 可以考慮改用鎖來(lái)保證操作的原子性
- 可以考慮合并多個(gè)變量,將多個(gè)變量封裝成一個(gè)對(duì)象,從 Java 1.5 開(kāi)始,JDK 提供了 AtomicReference 類(lèi)來(lái)保證引用對(duì)象之間的原子性,就可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行 CAS 操作。
4. synchronized、volatile、CAS 比較
(1)synchronized
是悲觀鎖,屬于搶占式,會(huì)引起其他線程阻塞。
(2)volatile
提供多線程共享變量可見(jiàn)性和禁止指令重排序優(yōu)化。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-458506.html
(3)CAS
是基于沖突檢測(cè)的樂(lè)觀鎖(非阻塞)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-458506.html
到了這里,關(guān)于【Java 并發(fā)編程】CAS 原理解析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!