JAVA LOCK 大全
[TOC]
一恰画、廣義分類:樂觀鎖/悲觀鎖
1.1 樂觀鎖的實(shí)現(xiàn)CAS (Compare and Swap)
樂觀鎖適合低并發(fā)的情況宏怔,在高并發(fā)的情況下由于自旋焰宣,性能甚至可能悲觀鎖更差蒿讥。
CAS是一種算法达椰,CAS(V,E,N)
,V:要更新的變量 E:預(yù)期值 N:新值挂绰。
- 如果多個(gè)線程進(jìn)行CAS操作屎篱,只有一個(gè)會(huì)成功,其余的會(huì)失斂佟(允許再次嘗試)交播。
- CAS是樂觀鎖的一種帶自選的實(shí)現(xiàn)算法(對(duì)象和類的關(guān)系)挺尿。
- 操作系統(tǒng)保證CAS的執(zhí)行是CPU原子指令咏删。
1.2 sun.misc.Unsafe
Java中CAS操作的執(zhí)行依賴于sun.misc.Unsafe類的方法,Unsafe中的方法都是native的栓辜。
- (Unsafe類永高,非線程安全隧土,擁有類似C的指針操作,Java官方不建議直接使用的Unsafe類)
//Usafe的幾個(gè)CAS方法
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
1.3 java.util.concurrent.atomic
并發(fā)包中的原子操作類(java.util.concurrent.atomic)命爬,在該包中提供了許多基于CAS實(shí)現(xiàn)的原子操作類曹傀。
這些方法都是基于調(diào)用Unsafe類實(shí)現(xiàn)的。
1.4 CAS的ABA問題 AtomicStampedReference&AtomicMarkableReference
ABA問題是反復(fù)讀寫問題饲宛,在多個(gè)線程并行時(shí)皆愉,一個(gè)線程把1改成2,另一個(gè)線程又把2改成1的情況。
CSA的ABA問題可以使用 AtomicStampedReference&AtomicMarkableReference兩個(gè)類來避免亥啦。
AtomicStampedReference 是一個(gè)帶有時(shí)間戳的對(duì)象引用。在每次修改后不僅會(huì)設(shè)置新值练链,還會(huì)記錄更改的時(shí)間翔脱。當(dāng)該類設(shè)置對(duì)象時(shí)必須同時(shí)滿足時(shí)間戳和期望值才能寫入成功。避免了反復(fù)讀寫問題媒鼓。
AtomicMarkableReference 是使用了一個(gè)bool值來標(biāo)記修改届吁,原理與AtomicStampedReference類似,不能避免ABA問題绿鸣,可以減少發(fā)生概率疚沐。
1.5 悲觀鎖(讀寫鎖是悲觀鎖的兩種實(shí)現(xiàn))
1.5.1 ReentrantReadWriteLock 可重入讀寫鎖
ReentrantReadWriteLock的構(gòu)造函數(shù)接受一個(gè)bool fair 用來指定是否是fair公平鎖
。默認(rèn)是unfair
.
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
使用讀寫鎖的時(shí)候,主動(dòng)加鎖(lock)潮模,一般在finally中釋放鎖(unlock)亮蛔。
1.5.2 Synchronized
經(jīng)過不斷的優(yōu)化(詳見 三、JAVA Synchronized 鎖的三種級(jí)別)擎厢,在低并發(fā)情況下性能很好究流。
二、Java鎖的兩種實(shí)現(xiàn):ReentrantLock 與 Synchronized
可重入鎖ReentrantLock 類實(shí)現(xiàn)了 Lock 动遭,它擁有與 synchronized 相同的并發(fā)性和內(nèi)存語義芬探。
添加了類似鎖投票、定時(shí)鎖等候和可中斷鎖等候的一些特性厘惦。
此外偷仿,它還提供了在激烈爭(zhēng)用情況下更佳的性能
(當(dāng)許多線程都想訪問共享資源時(shí),JVM 可以花更少的時(shí)候來調(diào)度線程宵蕉,把更多時(shí)間用在執(zhí)行線程上)
它有一個(gè)與鎖相關(guān)的獲取計(jì)數(shù)器酝静,如果擁有鎖的某個(gè)線程再次得到鎖,那么獲取計(jì)數(shù)器就加1国裳,然后鎖需要被釋放兩次才能獲得真正釋放形入。
這模仿了 synchronized 的語義:如果線程進(jìn)入由線程已經(jīng)擁有的監(jiān)控器保護(hù)的 synchronized 塊,就允許線程繼續(xù)進(jìn)行缝左,當(dāng)線程退出第二個(gè)(或者后續(xù)) synchronized 塊的時(shí)候亿遂,不釋放鎖,只有線程退出它進(jìn)入的監(jiān)控器保護(hù)的第一個(gè) synchronized 塊時(shí)渺杉,才釋放鎖蛇数。
IBM技術(shù)論壇中介紹 synchronized 和ReentrantLock的文章。(Jdk5)
文章的主要論述:synchronized 的功能集是 ReentrantLock 的子集是越。
ReentrantLock 多了:時(shí)間鎖等候耳舅、可中斷鎖等候、無塊結(jié)構(gòu)鎖、多個(gè)條件變量或者鎖投票等特性浦徊。
所以 ReentrantLock 從功能上來說完全可以取代 synchronized馏予。但是實(shí)際使用中不用這么絕對(duì)。
synchronized只有一個(gè)好處盔性,使用方便簡(jiǎn)單霞丧,不用主動(dòng)釋放鎖。
文章寫于jdk5時(shí)期冕香,jdk6給synchronized引入了偏向鎖等優(yōu)化蛹尝。性能差距越來越小。
所以除非用到ReentrantLock的獨(dú)有特性悉尾。其他情況下也可以繼續(xù)使用Synchronized.
三突那、synchronized 性能優(yōu)化:Synchronized的三種級(jí)別
無鎖、偏向构眯、輕量愕难、重量幾種級(jí)別的轉(zhuǎn)換圖如下:
3.1 Biased Locking 偏向鎖(輕量級(jí)鎖的多線程優(yōu)化技術(shù)jdk6引入)
是Java6引入的一項(xiàng)針對(duì)輕量級(jí)鎖的多線程優(yōu)化技術(shù)。
- 偏向鎖鸵赖,顧名思義务漩,它會(huì)偏向于第一個(gè)訪問鎖的線程,如果在運(yùn)行過程中它褪,同步鎖只有一個(gè)線程訪問饵骨,不存在多線程爭(zhēng)用的情況,則線程是不需要觸發(fā)同步的茫打,這種情況下居触,就會(huì)給線程加一個(gè)偏向鎖。
- 如果在運(yùn)行過程中老赤,遇到了其他線程搶占鎖轮洋,則持有偏向鎖的線程會(huì)被掛起,JVM會(huì)消除它身上的偏向鎖抬旺,將鎖恢復(fù)到標(biāo)準(zhǔn)的輕量級(jí)鎖弊予。
- 它通過消除資源無競(jìng)爭(zhēng)情況下的同步原語,進(jìn)一步提高了程序的運(yùn)行性能开财。但當(dāng)程序有大量競(jìng)爭(zhēng)情況汉柒,應(yīng)該關(guān)閉該特性。
//開啟偏向鎖
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
//關(guān)閉偏向鎖
-XX:-UseBiasedLocking
3.2 輕量級(jí)鎖
由偏向鎖升級(jí)责鳍,當(dāng)?shù)诙€(gè)線程加入鎖競(jìng)爭(zhēng)的時(shí)候碾褂,偏向鎖就升級(jí)為輕量級(jí)鎖。
加鎖過程:
- markWord鎖標(biāo)志位為無鎖狀態(tài)
01
時(shí)历葛,在當(dāng)前線程的棧幀中創(chuàng)建一個(gè)Lock Record 用來拷貝目前對(duì)象的markWord正塌。 - 拷貝成功后,JVM使用CAS嘗試將對(duì)象的markWord指向Lock Record。如果成功執(zhí)行3乓诽,失敗執(zhí)行4帜羊。
- 成功更新了markWord的指針后,該線程就有了該對(duì)象的鎖鸠天,會(huì)將markWord中的鎖標(biāo)志為設(shè)為
00
:輕量鎖逮壁。 - 更新失敗了,則先檢查對(duì)象的markWord是否指向該線程的棧幀(Stack里的)粮宛。如果是則其實(shí)已經(jīng)獲取鎖了,如果不是則說明多線程競(jìng)爭(zhēng)卖宠,則鎖膨脹為重量級(jí)鎖定
10
巍杈。
markWord存儲(chǔ)內(nèi)容(最后2bit是鎖狀態(tài)在無鎖和偏向鎖兩種狀態(tài)下,2bit前的1bit標(biāo)識(shí)是否偏向)
狀態(tài) | 鎖標(biāo)志位(2bit) | markWord存儲(chǔ)內(nèi)容 |
---|---|---|
未鎖定 | 01 | 對(duì)象哈希碼扛伍、對(duì)象分代年齡 |
輕量級(jí)鎖定 | 00 | 指向鎖記錄的指針 |
膨脹(重量級(jí)鎖定) | 10 | 執(zhí)行重量級(jí)鎖定的指針 |
GC標(biāo)記 | 11 | 空(不需要記錄信息) |
可偏向 | 01 | 偏向線程ID筷畦、偏向時(shí)間戳、對(duì)象分代年齡 |
具體的存儲(chǔ)內(nèi)容如下:
3.3 重量級(jí)鎖
重量級(jí)鎖發(fā)生在輕量鎖釋放鎖的期間刺洒,之前在獲取鎖的時(shí)候它拷貝了鎖對(duì)象頭的markWord鳖宾,在釋放鎖的時(shí)候如果它發(fā)現(xiàn)在它持有鎖的期間有其他線程來嘗試獲取鎖了,并且該線程對(duì)markWord做了修改逆航,兩者比對(duì)發(fā)現(xiàn)不一致鼎文,則切換到重量鎖。
四因俐、其他鎖:阻塞BlockingLock/自旋鎖SpinLock/公平fairLock /unfairLock/閉鎖Latch
4.1 阻塞鎖 Blocking lock
阻塞鎖會(huì)有線程切換的代價(jià)拇惋,但是阻塞鎖阻塞后不占用CPU。
阻塞鎖一般是悲觀鎖抹剩。
4.2 自旋鎖 Spin lock
- 自旋鎖原理非常簡(jiǎn)單撑帖,如果持有鎖的線程能在很短時(shí)間內(nèi)釋放鎖資源,那么那些等待競(jìng)爭(zhēng)鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞掛起狀態(tài)澳眷,它們只需要等一等(自旋)胡嘿,等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內(nèi)核的切換的消耗钳踊。
- 性能原因衷敌,一般JVM會(huì)限制自旋等待時(shí)間。
自旋鎖一般是樂觀鎖箍土。
4.2.1 自旋鎖優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):在鎖競(jìng)爭(zhēng)不激烈的情況下逢享,占用鎖的時(shí)間非常短的代碼來說,自旋操作(cpu空轉(zhuǎn))的消耗小于線程阻塞掛起的消耗吴藻。
- 缺點(diǎn):如果鎖競(jìng)爭(zhēng)激烈瞒爬,或者持有鎖的線程需要長(zhǎng)時(shí)間占用鎖執(zhí)行同步塊,就不適合自旋鎖,這是CPU空轉(zhuǎn)的消耗大于線程阻塞的消耗侧但。
Java線程切換的代價(jià):
Java的線程是映射到操作系統(tǒng)線程上的矢空,如果要阻塞或喚醒一個(gè)線程就需要操作系統(tǒng)介入,需要在用戶態(tài)與和心態(tài)之間切換禀横。
- 內(nèi)核態(tài): CPU可以訪問內(nèi)存所有數(shù)據(jù), 包括外圍設(shè)備, 例如硬盤, 網(wǎng)卡. CPU也可以將自己從一個(gè)程序切換到另一個(gè)程序
- 用戶態(tài): 只能受限的訪問內(nèi)存, 且不允許訪問外圍設(shè)備. 占用CPU的能力被剝奪, CPU資源可以被其他程序獲取
jdk1.6默認(rèn)開啟自旋鎖屁药,從JVM的層面對(duì)顯示鎖(都是悲觀鎖)做優(yōu)化,"智能"的決定自旋次數(shù)柏锄。
而樂觀鎖通過CAS實(shí)現(xiàn)酿箭,非阻塞,失敗后繼續(xù)獲取還是放棄的實(shí)現(xiàn)不確定趾娃,只能程序員從代碼層面對(duì)樂觀鎖做自旋(我稱之為自旋樂觀鎖)缭嫡。
4.3 fair/unfair
公平鎖,非公平鎖抬闷。
公平鎖維護(hù)了一個(gè)隊(duì)列妇蛀。要獲取鎖的線程來了都排隊(duì)。后續(xù)的線程按照隊(duì)列順序來獲取鎖笤成。
非公平鎖沒有維護(hù)隊(duì)列的開銷评架,沒有上下文切換的開銷,可能導(dǎo)致不公平炕泳,但是性能比fair好很多纵诞。
ReentrantLock的帶參構(gòu)造函數(shù)ReentrantLock(boolean fair)
可以指定實(shí)現(xiàn)公平還是非公平鎖。默認(rèn)是非公平鎖培遵。
4.4 閉鎖 Latch
閉鎖(Latch)是一種同步工具類挣磨,可以延遲線程的進(jìn)度直到其到達(dá)終止?fàn)顟B(tài)。
閉鎖的作用相當(dāng)于一扇門:在閉鎖到達(dá)結(jié)束狀態(tài)之前荤懂,這扇門一直是關(guān)閉的茁裙,并且沒有任何線程能通過,當(dāng)?shù)竭_(dá)結(jié)束狀態(tài)時(shí)节仿,這扇門會(huì)打開并允許所有的線程通過晤锥。
Java中CountDownLatch是一種閉鎖實(shí)現(xiàn),位于concurrent包下廊宪。
4.5 鎖消除
鎖消除指的是在JVM即使編譯時(shí)矾瘾,通過運(yùn)行少下文的掃描,去除不可能存在共享資源競(jìng)爭(zhēng)的鎖箭启。
通過鎖消除壕翩,可以節(jié)省毫無意義的鎖請(qǐng)求.
比如在單線程下使用StringBuffer,其中的同步完全沒有必要,這時(shí)候JVM可以在運(yùn)行時(shí)基于逃逸分析計(jì)數(shù)傅寡,消除不必要的鎖放妈。
五北救、如何避免死鎖
死鎖是類似這樣的情況:a,b兩個(gè)線程,a持有鎖A 等待鎖B;b持有鎖B等待鎖A芜抒。a,b相互等待珍策,誰也執(zhí)行不下去。
避免死鎖的原則是
- 避免持有多個(gè)鎖宅倒。
- 如果確實(shí)需要多個(gè)鎖攘宙,所有代碼都應(yīng)該按照相同的順序去申請(qǐng)鎖。