詳解java 中的CAS與ABA
1. 獨(dú)占鎖:
屬于悲觀鎖,有共享資源,需要加鎖時(shí),會(huì)以獨(dú)占鎖的方式導(dǎo)致其它需要獲取鎖才能執(zhí)行的線程掛起,等待持有鎖的錢程釋放鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨(dú)占鎖就是悲觀鎖的思想。
1.1 樂(lè)觀鎖的操作
多線程并發(fā)修改一個(gè)值時(shí)的實(shí)現(xiàn):
public class SimulatedCAS {
//加volatile的目的是利用其happens-before原則,保證線程可見性
private volatile int value;
public synchronized int getValue() { return value; }
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (value == expectedValue)
value = newValue;
return oldValue;
}
}
2. 樂(lè)觀鎖:
總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)機(jī)制和CAS算法實(shí)現(xiàn)。樂(lè)觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫(kù)提供的類似于write_condition機(jī)制,其實(shí)都是提供的樂(lè)觀鎖。 在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂(lè)觀鎖的一種實(shí)現(xiàn)方式CAS實(shí)現(xiàn)的。樂(lè)觀鎖一般會(huì)使用版本號(hào)機(jī)制或CAS算法實(shí)現(xiàn)。
2.1 CAS操作
- CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值。否則,處理器不做任何操作。無(wú)論哪種情況,它都會(huì)在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當(dāng)前值。)CAS 有效地說(shuō)明了“我認(rèn)為位置 V 應(yīng)該包含值 A;如果包含該值,則將 B 放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現(xiàn)在的值即可?!?/li>
- 通常將 CAS 用于同步的方式是從地址 V 讀取值 A,執(zhí)行多步計(jì)算來(lái)獲得新值 B,然后使用 CAS 將 V 的值從 A 改為 B。如果 V 處的值尚未同時(shí)更改,則 CAS 操作成功。
- 類似于 CAS 的指令允許算法執(zhí)行讀-修改-寫操作,而無(wú)需害怕其他線程同時(shí)修改變量,因?yàn)槿绻渌€程修改變量,那么 CAS 會(huì)檢測(cè)它(并失?。?,算法可以對(duì)該操作重新計(jì)算。 CAS實(shí)現(xiàn)計(jì)數(shù)器的操作:
public class CasCounter {
private SimulatedCAS value;
public int getValue() {
return value.getValue();
}
public int increment() {
int oldValue = value.getValue();
while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue)
oldValue = value.getValue();
return oldValue + 1;
}
}
3. 原子變量類
JDK5.0之后加入了java.util.concurrent.atomic 包,其中的AtomicInteger; AtomicLong; AtomicReference; AtomicBoolean 等都是在CAS基礎(chǔ)上實(shí)現(xiàn)的。
4. CAS的缺陷
- 循環(huán)時(shí)間太長(zhǎng),如果自旋長(zhǎng)時(shí)間不成功,會(huì)給cpu帶來(lái)極大的開銷,有興趣的可以使用JMH測(cè)試下AtomicLong 和 LongAdder的性能。
- ABA問(wèn)題: CAS需要檢查待操作值有沒(méi)有發(fā)生改變,如果沒(méi)有發(fā)生改變則更新。 但是存在這樣一種情況:如果一個(gè)值原來(lái)是A,變成了B,然后又變成了A,那么在CAS檢查的時(shí)候會(huì)發(fā)現(xiàn)沒(méi)有改變,但是實(shí)質(zhì)上它已經(jīng)發(fā)生了改變,這就是所謂的ABA問(wèn)題。 在運(yùn)用CAS做Lock-Free操作中有一個(gè)經(jīng)典的ABA問(wèn)題:比如線程1從內(nèi)存位置V中取出A,這時(shí)另一個(gè)線程2也從內(nèi)存中取出A,并且線程2進(jìn)行了操作之后變成了B,然線程2又將V位置數(shù)據(jù)變成了A,這時(shí)候線程1進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后線程1 操作成功。看上去是成功了,實(shí)際上有隱藏的問(wèn)題: 現(xiàn)有一個(gè)用單向鏈表實(shí)現(xiàn)的FIFO堆棧,棧頂為A,這時(shí)線程1已經(jīng)知道A.next為B,然后希望用CAS將棧頂替換為B,在線程1執(zhí)行上面這條指令之前,線程2 介入,將A、B出棧,再push D、C、A,此時(shí)A位于棧頂,B已經(jīng)不在棧中;此時(shí)線程1執(zhí)行CAS,發(fā)現(xiàn)棧頂仍為A,所以CAS成功,即將棧頂變成B,但實(shí)際上此時(shí)B與 當(dāng)前棧中元素D、C沒(méi)有關(guān)系,B.next為null,這樣一來(lái)就直接把C、D丟掉了。 對(duì)于ABA問(wèn)題其解決方案是加上版本號(hào),即在每個(gè)變量都加上一個(gè)版本號(hào),每次改變時(shí)加1,即A —> B —> A,變成A(1) —> B(2) —> A(3)。 java中AtomicStampedReference也實(shí)現(xiàn)了這個(gè)作用,它通過(guò)包裝[E,Integer]的元組來(lái)對(duì)對(duì)象標(biāo)記版本戳stamp,從而避免ABA問(wèn)題。
public class AtomicTest {
private static AtomicInteger atomicInteger = new AtomicInteger(100);
private static AtomicStampedReference<Integer> atomicStampedReference =
new AtomicStampedReference<Integer>(99, 0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
atomicInteger.compareAndSet(99, 100);
atomicInteger.compareAndSet(100, 99);
});
Thread thread2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
boolean b = atomicInteger.compareAndSet(99, 100);
System.out.println(b);
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
Thread refT1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(99, 100,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
atomicStampedReference.compareAndSet(100, 99,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
});
Thread refT2 = new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("before sleep : stamp = " + stamp); // stamp = 0
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after sleep : stamp = " + atomicStampedReference.getStamp());//stamp = 1
boolean c3 = atomicStampedReference.compareAndSet(99, 100, stamp, stamp+1);
System.out.println(c3); //false
});
refT1.start();
refT2.start();
}
}
結(jié)果如下:
true before sleep : stamp = 0 after sleep : stamp = 2 false
也就是說(shuō)AtomicInteger更新成功,而AtomicStampedReference更新失敗。
以上就是詳解java 中的CAS與ABA的詳細(xì)內(nèi)容,更多關(guān)于java 中的CAS與ABA的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring?Security?過(guò)濾器注冊(cè)脈絡(luò)梳理
這篇文章主要介紹了Spring?Security過(guò)濾器注冊(cè)脈絡(luò)梳理,Spring?Security在Servlet的過(guò)濾鏈中注冊(cè)了一個(gè)過(guò)濾器FilterChainProxy,它會(huì)把請(qǐng)求代理到Spring?Security自己維護(hù)的多個(gè)過(guò)濾鏈,每個(gè)過(guò)濾鏈會(huì)匹配一些URL,如果匹配則執(zhí)行對(duì)應(yīng)的過(guò)濾器2022-08-08
Spring中的攔截器HandlerInterceptor詳細(xì)解析
這篇文章主要介紹了Spring中的攔截器HandlerInterceptor詳細(xì)解析,HandlerInterceptor 是 Spring 框架提供的一個(gè)攔截器接口,用于在請(qǐng)求處理過(guò)程中攔截和處理請(qǐng)求,需要的朋友可以參考下2024-01-01
SpringBoot自動(dòng)配置Quartz的實(shí)現(xiàn)步驟
本文主要介紹了SpringBoot自動(dòng)配置Quartz的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Java高級(jí)特性之反射機(jī)制實(shí)例詳解
這篇文章主要介紹了Java高級(jí)特性之反射機(jī)制,結(jié)合實(shí)例形式詳細(xì)分析了Java反射機(jī)制原理、功能、使用方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2018-08-08
Java中生成不重復(fù)隨機(jī)數(shù)的四種方法舉例詳解
在Java編程中獲取隨機(jī)數(shù)是常見的需求,這篇文章主要介紹了Java中生成不重復(fù)隨機(jī)數(shù)的四種方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-04-04
java使用poi在excel單元格添加超鏈接設(shè)置字體顏色的方法
這篇文章主要介紹了java使用poi在excel單元格添加超鏈接,設(shè)置字體顏色,poi功能還是很強(qiáng)大的,基本能想到的功能都能通過(guò)poi實(shí)現(xiàn),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09

