寫(xiě)在最前面
在上文java并發(fā)之volatile末尾有提到张弛,volatile并不能保證++
操作的線程安全鉴逞。我們來(lái)通過(guò)一個(gè)簡(jiǎn)單的例子看下為什么恶耽。
通過(guò)
javap -v
看下其反編譯后字節(jié)碼指令:從反編譯結(jié)果可以看出蛹含,++被拆成了這樣三個(gè)指令:
getfield
:獲取變量count中的值;iadd
:進(jìn)行+1操作撒踪;putfield
:將+1后的數(shù)據(jù)寫(xiě)回count过咬。
volatile雖然可以保證變量的可見(jiàn)性,但是并不能保證這三個(gè)操作的原子性制妄,所以它不能保證++
操作的線程安全掸绞,那++
操作的線程安全要怎么解決呢?
注:何謂原子性忍捡?
原子性是指一個(gè)操作不能被中斷集漾,即使是在多個(gè)線程一起執(zhí)行的時(shí)候,一旦開(kāi)始砸脊,也不會(huì)被其他線程打斷具篇。
第一種簡(jiǎn)單粗暴的方法就是加鎖了,但是這樣做開(kāi)銷(xiāo)有點(diǎn)大了耶凌埂;
第二種方法就是使用JDK的CAS了驱显,juc包下也有相關(guān)Atomic類可以支持,使用簡(jiǎn)單瞳抓,而且高效埃疫。(哈哈哈,終于繞回了本文的主題啊孩哑,不容易啊~~~)
接下來(lái)我們就通過(guò)分析CAS相關(guān)源碼實(shí)現(xiàn)來(lái)看看CAS是如何做到線程安全的栓霜。
CAS原理
CAS,Compare and swap横蜒,比較和替換胳蛮。CAS的操作包括三個(gè)參數(shù):內(nèi)存位置(V)、預(yù)期原值(A)和新的值(B)丛晌,如果V和A相同仅炊,處理器會(huì)將該位置的值修改為B,否則澎蛛,處理器不會(huì)做任何操作抚垄。
在前文java并發(fā)編程之原子類已經(jīng)詳細(xì)介紹了JDK提供的Atomic相關(guān)原子類的使用及實(shí)現(xiàn),它們的實(shí)現(xiàn)都很神似谋逻,都是通過(guò)Unsafe
提供的compareAndSwap
開(kāi)頭的方法來(lái)完成對(duì)value的并發(fā)修改的呆馁。接下來(lái)我們就以compareAndSwapInt
方法為例來(lái)看看它到底是怎么保證并發(fā)修改的安全性。
compareAndSwapInt實(shí)現(xiàn)
從聲明可以看出毁兆,compareAndSwapInt是一個(gè)native的方法智哀,該方法的實(shí)現(xiàn)位于unsafe.cpp
中:
從源碼可以看出,compareAndSwapInt主要做了這些事情:
獲取value在內(nèi)存中的地址荧恍;
調(diào)用
Atomic::cmpxchg
方法完成比較替換瓷叫,其中e是原始值屯吊,x是新的值。
接下來(lái)我們來(lái)看看Atomic::cmpxchg
的相關(guān)實(shí)現(xiàn):
注:以linux x86平臺(tái)為例摹菠,具體實(shí)現(xiàn)在
atomic_linux_x86.inline.hpp中
在開(kāi)始介紹源碼之前先科普下這里用到的內(nèi)嵌匯編的關(guān)鍵字:
__asm__
:用來(lái)聲明一個(gè)內(nèi)聯(lián)匯編表達(dá)式盒卸,任何一個(gè)內(nèi)聯(lián)匯編表達(dá)式都是以它開(kāi)頭的,是必不可少的次氨;__volatile__
或者volatile
:__volatile__
是GCC關(guān)鍵字volatile
的宏定義蔽介,如果用了它,則是向GCC聲明不允許對(duì)該內(nèi)聯(lián)匯編優(yōu)化煮寡,否則當(dāng)使用了優(yōu)化選項(xiàng)(-O)進(jìn)行編譯時(shí)虹蓄,GCC將會(huì)根據(jù)自己的判斷決定是否將這個(gè)內(nèi)聯(lián)匯編表達(dá)式中的指令優(yōu)化掉。
有了這些科普我們?cè)賮?lái)看代碼是不是就和諧多了幸撕,Atomic::cmpxchg
其實(shí)就做了這樣幾件事:
判斷當(dāng)前系統(tǒng)是否是多核處理器薇组;
-
依賴指令
cmpxchg
完成比較替換,如果當(dāng)前系統(tǒng)是多核處理器坐儿,將會(huì)在指令前加lock
前綴律胀,否則不加。其中具體是否要加lock
前綴的處理在宏LOCK_IF_MP
中定義:
LICK_IF_MP聲明
lock
前綴保證了CAS并發(fā)操作的安全性貌矿。
注:在上文java并發(fā)之volatile中已經(jīng)詳細(xì)介紹過(guò)lock前綴的意義炭菌,在這里我就不再贅述了,有需要了解的小伙伴請(qǐng)自行移步啦~
CAS存在的問(wèn)題
CAS雖然可以很高效原子操作逛漫,但是它也是存在問(wèn)題的黑低。
ABA問(wèn)題
因?yàn)镃AS會(huì)在更新值的時(shí)候會(huì)檢查值有沒(méi)有發(fā)生改變,如果沒(méi)有發(fā)生改變就更新酌毡,否則不更新克握,但是如果一個(gè)值原來(lái)是A,變成了B阔馋,再變回A玛荞,這時(shí)候CAS進(jìn)行檢查時(shí)會(huì)誤認(rèn)為它沒(méi)有發(fā)生變化娇掏。針對(duì)這種情況呕寝,juc包提供了AtomicStampedReference
,通過(guò)控制變量值的版本號(hào)來(lái)解決ABA問(wèn)題婴梧。循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大
自旋CAS如果長(zhǎng)時(shí)間不成功會(huì)給CPU帶來(lái)比較大的執(zhí)行開(kāi)銷(xiāo)下梢。只能保證一個(gè)共享變量的原子操作
對(duì)一個(gè)共享變量的操作,使用循環(huán)CAS肯定可以保證其原子性塞蹭,但是如果對(duì)多個(gè)變量操作時(shí)孽江,CAS就比較雞肋了。當(dāng)然有一個(gè)辦法就是把幾個(gè)共享變量合并成一個(gè)大的對(duì)象番电,然后CAS操作這個(gè)大對(duì)象岗屏。同樣辆琅,juc包也提供AtomicReference
用于更新對(duì)象。