獨(dú)占鎖(悲觀鎖)與樂(lè)觀鎖
在多線程編程的時(shí)候惋啃,為了保證多個(gè)線程對(duì)一個(gè)對(duì)象可以安全進(jìn)行訪問(wèn)時(shí),我們需要加同步鎖synchronized,保證對(duì)象的在使用時(shí)的正確性,synchronized就是一種獨(dú)占鎖,它會(huì)導(dǎo)致所有需要此鎖的線程掛起,等待鎖的釋放。
加鎖會(huì)導(dǎo)致一下問(wèn)題:
加多線程競(jìng)爭(zhēng)下失驶,加鎖和釋放鎖會(huì)導(dǎo)致較多的上下文切換,引起性能問(wèn)題枣购。
多線程可以導(dǎo)致死鎖的問(wèn)題嬉探。
多線程持有的鎖會(huì)導(dǎo)致其他需要此鎖的線程掛起。
樂(lè)觀鎖(CAS)的思想?yún)s是不加鎖棉圈,那不加鎖如何確保某一變量的操作沒(méi)有被其他線程修改過(guò)涩堤?
這里就需要CAS操作(CompareAndSwap)來(lái)實(shí)現(xiàn)
CAS有三個(gè)操作參數(shù):內(nèi)存地址,期望值分瘾,要修改的新值胎围,當(dāng)期望值和內(nèi)存當(dāng)中的值進(jìn)行比較不相等的時(shí)候,表示內(nèi)存中的值已經(jīng)被別線程改動(dòng)過(guò)芹敌,這時(shí)候失敗返回痊远,只有相等時(shí),才會(huì)將內(nèi)存中的值改為新的值氏捞,并返回成功。
這里我們可以看一下JAVA的原子類AtomicLong.getAndIncrement()的實(shí)現(xiàn)冒版,來(lái)理解一下CAS這一樂(lè)觀鎖(JDK 1.8)
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
接著看一下 Unsafe.getAndAddLong()的實(shí)現(xiàn):
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
這里我們可以看到AtomicLong.getAndIncrement()的實(shí)現(xiàn)就是通過(guò)CAS循環(huán)操作的實(shí)現(xiàn)液茎,只有期望值與真實(shí)值相同情況下,CAS操作才會(huì)成功執(zhí)行辞嗡,退出循環(huán)捆等,如果失敗則繼續(xù)自旋,直到成功续室。
具體流程如下
ABA問(wèn)題
ABA問(wèn)題是指在CAS操作時(shí)栋烤,其他線程將變量值A(chǔ)改為了B,但是又被改回了A挺狰,等到本線程使用期望值A(chǔ)與當(dāng)前變量進(jìn)行比較時(shí)明郭,發(fā)現(xiàn)變量A沒(méi)有變买窟,于是CAS就將A值進(jìn)行了交換操作,但是實(shí)際上該值已經(jīng)被其他線程改變過(guò)薯定,這與樂(lè)觀鎖的設(shè)計(jì)思想不符合始绍。ABA問(wèn)題的解決思路是,每次變量更新的時(shí)候把變量的版本號(hào)加1话侄,那么A-B-A就會(huì)變成A1-B2-A3亏推,只要變量被某一線程修改過(guò),改變量對(duì)應(yīng)的版本號(hào)就會(huì)發(fā)生遞增變化年堆,從而解決了ABA問(wèn)題吞杭。在JDK的java.util.concurrent.atomic包中提供了AtomicStampedReference來(lái)解決ABA問(wèn)題,該類的compareAndSet是該類的核心方法变丧,實(shí)現(xiàn)如下:
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
我們可以發(fā)現(xiàn)芽狗,該類檢查了當(dāng)前引用與當(dāng)前標(biāo)志是否與預(yù)期相同,如果全部相等锄贷,才會(huì)以原子方式將該引用和該標(biāo)志的值設(shè)為新的更新值译蒂,這樣CAS操作中的比較就不依賴于變量的值了。
具體流程如下