樂觀鎖與悲觀鎖
悲觀鎖
總是假設(shè)最壞的情況刻恭,每次獲取數(shù)據(jù)都認為別人會修改,所以拿數(shù)據(jù)時會上鎖,一直到釋放鎖不允許其他線程修改數(shù)據(jù)。Java中如synchronized和reentrantLock就是這種實現(xiàn)冰蘑。
樂觀鎖
總是假設(shè)最好的情況,每次去拿數(shù)據(jù)時都認為別人不會修改村缸,所以不上鎖祠肥,等更新數(shù)據(jù)時判斷一下在此期間是否有其他人更新過這個數(shù)據(jù),可以使用CAS算法實現(xiàn)梯皿。樂觀鎖適用于多讀少寫的應(yīng)用類型仇箱,可以大幅度提高吞吐量县恕。樂觀鎖的實現(xiàn)機制主要包括版本號機制(給數(shù)據(jù)加一個版本號,數(shù)據(jù)被修改版本號會加一剂桥,更新時讀取版本號忠烛,若讀取到的版本號和之前一致才更新,否則駁回)和CAS算法(下詳)权逗。
CAS算法
JDK5增加java.util.concurrent包美尸,其下類通過CAS算法實現(xiàn)了樂觀鎖實現(xiàn)。
CAS即compare and swap斟薇,是一種有名的無鎖算法师坎,即不使用鎖的情況下實現(xiàn)多線程之間的變量同步,也叫非阻塞同步堪滨。
CAS有三個操作數(shù)胯陋,內(nèi)存值V,舊的預(yù)期內(nèi)存值A(chǔ)袱箱,要修改的新值B遏乔,當且僅當A=V,才將內(nèi)存值V修改為B犯眠,否則不會執(zhí)行任何操作按灶。一般情況下CAS是一個自旋操作,即不斷重試筐咧。
CAS只能保證一個共享變量的原子操作鸯旁,當操作涉及跨多個共享變量時CAS無效。但可用AtomicReference來保證引用對象之間的原子性量蕊。
CAS開銷
CAS是CPU指令集的操作铺罢,只有一步的原子操作,所以非巢信冢快韭赘,但CAS的開銷主要在于cache miss問題。如圖
這是一個8核CPU系統(tǒng)势就,共有4個管芯泉瞻,每個管芯中有兩個CPU,每個CPU有cache苞冯,管芯內(nèi)有一個互聯(lián)模塊袖牙,讓管芯的兩個核可以互相通信。圖中的系統(tǒng)連接模塊可以讓四個管芯通信舅锄。例如鞭达,此時CPU0進行一個CAS操作,而該變量所在的緩存線在CPU7的高速緩存中,則流程如下:
- CPU檢查本地緩存畴蹭,沒有找到緩存線坦仍。
- 請求被轉(zhuǎn)發(fā)到CPU0和CPU1的互聯(lián)模塊,檢查CPU1的本地高速緩存叨襟,沒有找到緩存線繁扎。
- 請求被轉(zhuǎn)發(fā)到系統(tǒng)互聯(lián)模塊,檢查其他三個管芯芹啥,得知緩存線在CPU6和CPU7所在的管芯中锻离。
- 請求被轉(zhuǎn)發(fā)到CPU6和CPU7的互聯(lián)模塊,檢查這兩個CPU的高速緩存墓怀,在CPU7中找到緩存線汽纠。
- CPU7將緩存線發(fā)給互聯(lián)模塊,并刷新自己的緩存線傀履。
- CPU6和CPU7的互聯(lián)模塊將緩存線發(fā)送給系統(tǒng)互聯(lián)模塊虱朵。
- 系統(tǒng)互聯(lián)模塊將緩存線發(fā)送給CPU0和CPU1的互聯(lián)模塊。
- CPU0對高速緩存中的變量致性CAS操作钓账。
此外碴犬,資源競爭時,CAS會自旋(不成功就循環(huán)到成功)梆暮,
ABA問題
CAS一個常見問題是ABA問題服协,即變量V初次讀時是A值,被賦值時也是A值啦粹,但期間變量被賦值成B值,CAS會誤認為他從沒被修改過偿荷。JDK1.5后的AtomicStampedReference類提供了監(jiān)測ABA問題的能力,其中的compareAndSet方法首先檢查當前引用是否等于預(yù)期引用唠椭,并且當前標志等于預(yù)期標志跳纳,全部相等則以原子方式將該引用和該標志的值設(shè)置為給定的更新值。
CAS與synchronized
- 資源競爭少時贪嫂,synchronized同步鎖進行線程阻塞寺庄,喚醒切換,用戶內(nèi)核態(tài)間切換力崇,浪費額外CPU資源斗塘,CAS基于硬件實現(xiàn),不進入內(nèi)核亮靴,不切換線程逛拱,操作自旋幾率小,CAS有更高的性能台猴。
- 資源競爭嚴重時,CAS自旋概率較大,從而浪費更多的CPU資源饱狂,效率低于synchronized曹步。
java.util.concurrent.atomic
jdk1.5提供了一組原子類,由CAS對其實現(xiàn)休讳。其中的類可以分為四組:
- AtomicBoolean讲婚,AtomicInteger,AtomicLong俊柔,AtomicReference
- AtomicIntegerArray筹麸,AtomicLongArray
- AtomicLongFieldUpdater,AtomicIntegerFieldUpdater雏婶,AtomicReferenceFieldUpdater
- AtomicMarkableReference物赶,AtomicStampedReference,AtomicReferenceArray
其作用為對單一數(shù)據(jù)的操作實現(xiàn)原子化留晚,無需阻塞代碼酵紫,但訪問兩個或兩個以上的atomic變量或?qū)蝹€atomic變量進行2次或2次以上的操作被認為是需要同步的以便這些操作是一個原子操作。
AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference
這四種類型用來處理Boolean错维,Integer, Long, 對象奖地,包含以下方法:
- 構(gòu)造函數(shù),默認值分別為false, 0, 0, null赋焕。帶參數(shù)則參數(shù)為初始化數(shù)據(jù)参歹。
- set()和get()方法,常規(guī)的設(shè)置/讀取值隆判,非原子操作犬庇。
- lazySet(),設(shè)置值蜜氨,原子操作械筛。
- getAndSet()相當于先使用get再set,但是是一個原子操作飒炎。
- compareAndSet(expectedData, newData)埋哟,接受兩個參數(shù),若atomic內(nèi)數(shù)據(jù)和期望數(shù)據(jù)一致郎汪,則將新數(shù)據(jù)賦值給atomic數(shù)據(jù)返回true赤赊,否則不設(shè)置并返回false。
- weakCompareAndSet(expectedData, newData)煞赢,與前者類似抛计,但更高效,不同的是可能會返回虛假的失敗照筑,不提供排序的保證吹截,最好用于五關(guān)于happens-before的程序瘦陈。
對于AtomicInteger, AtomicLong,還實現(xiàn)了getAndIncrement(),increateAndGet(),getAndDecreate(),decreateAndGet(),addAndGet()波俄,getAndAdd()方法晨逝,以實現(xiàn)加減法的原子操作。剩下部分代碼我還在閱讀中懦铺,不定期補充捉貌。
參考文獻
深入理解CAS算法原理
面試必備之樂觀鎖與悲觀鎖
Java之多線程 Atomic(原子的)
對 volatile、compareAndSet冬念、weakCompareAndSet 的一些思考