java.util.concurrent包的最底層基礎(chǔ)CAS技術(shù)渗常,原理很簡(jiǎn)單。
CAS有3個(gè)操作數(shù)由缆,內(nèi)存值V注祖,舊的預(yù)期值A(chǔ)猾蒂,要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí)是晨,將內(nèi)存值V修改為B肚菠,否則什么都不做。
引發(fā)ABA問(wèn)題:如果變量V初次讀取的時(shí)候是A罩缴,并且在準(zhǔn)備賦值的時(shí)候檢查到它仍然是A蚊逢,那能說(shuō)明它的值沒(méi)有被其他線程修改過(guò)了嗎?如果在這段期間它的值曾經(jīng)被改成了B箫章,然后又改回A烙荷,那CAS操作就會(huì)誤認(rèn)為它從來(lái)沒(méi)有被修改過(guò)。
舉例:
線程1準(zhǔn)備用CAS將變量的值由A替換為B檬寂,在此之前终抽,線程2將變量的值由A替換為C,又由C替換為A桶至,然后線程1執(zhí)行CAS時(shí)發(fā)現(xiàn)變量的值仍然為A昼伴,所以CAS成功。但實(shí)際上這時(shí)的現(xiàn)場(chǎng)已經(jīng)和最初不同了镣屹,盡管CAS成功圃郊,但可能存在潛藏的問(wèn)題,例如下面的例子:
現(xiàn)有一個(gè)用單向鏈表實(shí)現(xiàn)的堆棧野瘦,棧頂為A描沟,這時(shí)線程T1已經(jīng)知道A.next為B,然后希望用CAS將棧頂替換為B:
head.compareAndSet(A,B);
在T1執(zhí)行上面這條指令之前鞭光,線程T2介入吏廉,將A、B出棧惰许,再pushD席覆、C、A汹买,此時(shí)堆棧結(jié)構(gòu)如下圖佩伤,而對(duì)象B此時(shí)處于游離狀態(tài):
此時(shí)輪到線程T1執(zhí)行CAS操作,檢測(cè)發(fā)現(xiàn)棧頂仍為A晦毙,所以CAS成功生巡,棧頂變?yōu)锽,但實(shí)際上B.next為null见妒,所以此時(shí)的情況變?yōu)椋?/p>
其中堆棧中只有B一個(gè)元素孤荣,C和D組成的鏈表不再存在于堆棧中,平白無(wú)故就把C、D丟掉了盐股。
解決辦法:針對(duì)這種情況钱豁,java并發(fā)包中提供了一個(gè)帶有標(biāo)記的原子引用類"AtomicStampedReference"(版本戳),它可以通過(guò)控制變量值的版本來(lái)保證CAS的正確性疯汁。