Java鎖:悲觀/樂觀/阻塞/自旋/公平鎖/閉鎖,鎖消除CAS及synchronized的三種鎖級別

JAVA LOCK 大全

[TOC]

一、廣義分類:樂觀鎖/悲觀鎖

1.1 樂觀鎖的實現CAS (Compare and Swap)

樂觀鎖適合低并發的情況,在高并發的情況下由于自旋,性能甚至可能悲觀鎖更差。

CAS是一種算法,CAS(V,E,N),V:要更新的變量 E:預期值 N:新值。

  • 如果多個線程進行CAS操作,只有一個會成功,其余的會失?。ㄔ试S再次嘗試)。
  • CAS是樂觀鎖的一種帶自選的實現算法(對象和類的關系)。
  • 操作系統保證CAS的執行是CPU原子指令。

1.2 sun.misc.Unsafe

Java中CAS操作的執行依賴于sun.misc.Unsafe類的方法,Unsafe中的方法都是native的。

  • (Unsafe類,非線程安全,擁有類似C的指針操作,Java官方不建議直接使用的Unsafe類)
    //Usafe的幾個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

并發包中的原子操作類(java.util.concurrent.atomic),在該包中提供了許多基于CAS實現的原子操作類。
這些方法都是基于調用Unsafe類實現的。

1.4 CAS的ABA問題 AtomicStampedReference&AtomicMarkableReference

  1. ABA問題是反復讀寫問題,在多個線程并行時,一個線程把1改成2,另一個線程又把2改成1的情況。

  2. CSA的ABA問題可以使用 AtomicStampedReference&AtomicMarkableReference兩個類來避免。

  3. AtomicStampedReference 是一個帶有時間戳的對象引用。在每次修改后不僅會設置新值,還會記錄更改的時間。當該類設置對象時必須同時滿足時間戳和期望值才能寫入成功。避免了反復讀寫問題。

  4. AtomicMarkableReference 是使用了一個bool值來標記修改,原理與AtomicStampedReference類似,不能避免ABA問題,可以減少發生概率。

1.5 悲觀鎖(讀寫鎖是悲觀鎖的兩種實現)

1.5.1 ReentrantReadWriteLock 可重入讀寫鎖

ReentrantReadWriteLock的構造函數接受一個bool fair 用來指定是否是fair公平鎖。默認是unfair.

    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

使用讀寫鎖的時候,主動加鎖(lock),一般在finally中釋放鎖(unlock)。

1.5.2 Synchronized

經過不斷的優化(詳見 三、JAVA Synchronized 鎖的三種級別),在低并發情況下性能很好。

二、Java鎖的兩種實現:ReentrantLock 與 Synchronized

可重入鎖ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的并發性和內存語義。
添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。

此外,它還提供了在激烈爭用情況下更佳的性能
(當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上)

它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。
這模仿了 synchronized 的語義:如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者后續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。

IBM技術論壇中介紹 synchronized 和ReentrantLock的文章。(Jdk5)
文章的主要論述:synchronized 的功能集是 ReentrantLock 的子集。
ReentrantLock 多了:時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票等特性。
所以 ReentrantLock 從功能上來說完全可以取代 synchronized。但是實際使用中不用這么絕對。
synchronized只有一個好處,使用方便簡單,不用主動釋放鎖。

文章寫于jdk5時期,jdk6給synchronized引入了偏向鎖等優化。性能差距越來越小。
所以除非用到ReentrantLock的獨有特性。其他情況下也可以繼續使用Synchronized.

三、synchronized 性能優化:Synchronized的三種級別

無鎖、偏向、輕量、重量幾種級別的轉換圖如下:


sync鎖級別轉化.png

3.1 Biased Locking 偏向鎖(輕量級鎖的多線程優化技術jdk6引入)

是Java6引入的一項針對輕量級鎖的多線程優化技術。

  • 偏向鎖,顧名思義,它會偏向于第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,這種情況下,就會給線程加一個偏向鎖。
  • 如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。
  • 它通過消除資源無競爭情況下的同步原語,進一步提高了程序的運行性能。但當程序有大量競爭情況,應該關閉該特性。
//開啟偏向鎖
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
//關閉偏向鎖
-XX:-UseBiasedLocking

3.2 輕量級鎖

由偏向鎖升級,當第二個線程加入鎖競爭的時候,偏向鎖就升級為輕量級鎖。
加鎖過程:

  1. markWord鎖標志位為無鎖狀態01時,在當前線程的棧幀中創建一個Lock Record 用來拷貝目前對象的markWord。
  2. 拷貝成功后,JVM使用CAS嘗試將對象的markWord指向Lock Record。如果成功執行3,失敗執行4。
  3. 成功更新了markWord的指針后,該線程就有了該對象的鎖,會將markWord中的鎖標志為設為00:輕量鎖。
  4. 更新失敗了,則先檢查對象的markWord是否指向該線程的棧幀(Stack里的)。如果是則其實已經獲取鎖了,如果不是則說明多線程競爭,則鎖膨脹為重量級鎖定10。

markWord存儲內容(最后2bit是鎖狀態在無鎖和偏向鎖兩種狀態下,2bit前的1bit標識是否偏向)

狀態 鎖標志位(2bit) markWord存儲內容
未鎖定 01 對象哈希碼、對象分代年齡
輕量級鎖定 00 指向鎖記錄的指針
膨脹(重量級鎖定) 10 執行重量級鎖定的指針
GC標記 11 空(不需要記錄信息)
可偏向 01 偏向線程ID、偏向時間戳、對象分代年齡

具體的存儲內容如下:


markWord_lock.jpg

3.3 重量級鎖

重量級鎖發生在輕量鎖釋放鎖的期間,之前在獲取鎖的時候它拷貝了鎖對象頭的markWord,在釋放鎖的時候如果它發現在它持有鎖的期間有其他線程來嘗試獲取鎖了,并且該線程對markWord做了修改,兩者比對發現不一致,則切換到重量鎖。

四、其他鎖:阻塞BlockingLock/自旋鎖SpinLock/公平fairLock /unfairLock/閉鎖Latch

4.1 阻塞鎖 Blocking lock

阻塞鎖會有線程切換的代價,但是阻塞鎖阻塞后不占用CPU。
阻塞鎖一般是悲觀鎖。

4.2 自旋鎖 Spin lock

  • 自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內釋放鎖資源,那么那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內核的切換的消耗。
  • 性能原因,一般JVM會限制自旋等待時間。
    自旋鎖一般是樂觀鎖。

4.2.1 自旋鎖優缺點

  • 優點:在鎖競爭不激烈的情況下,占用鎖的時間非常短的代碼來說,自旋操作(cpu空轉)的消耗小于線程阻塞掛起的消耗。
  • 缺點:如果鎖競爭激烈,或者持有鎖的線程需要長時間占用鎖執行同步塊,就不適合自旋鎖,這是CPU空轉的消耗大于線程阻塞的消耗。

Java線程切換的代價:
Java的線程是映射到操作系統線程上的,如果要阻塞或喚醒一個線程就需要操作系統介入,需要在用戶態與和心態之間切換。

  • 內核態: CPU可以訪問內存所有數據, 包括外圍設備, 例如硬盤, 網卡. CPU也可以將自己從一個程序切換到另一個程序
  • 用戶態: 只能受限的訪問內存, 且不允許訪問外圍設備. 占用CPU的能力被剝奪, CPU資源可以被其他程序獲取

jdk1.6默認開啟自旋鎖,從JVM的層面對顯示鎖(都是悲觀鎖)做優化,"智能"的決定自旋次數。
而樂觀鎖通過CAS實現,非阻塞,失敗后繼續獲取還是放棄的實現不確定,只能程序員從代碼層面對樂觀鎖做自旋(我稱之為自旋樂觀鎖)。

4.3 fair/unfair

公平鎖,非公平鎖。
公平鎖維護了一個隊列。要獲取鎖的線程來了都排隊。后續的線程按照隊列順序來獲取鎖。
非公平鎖沒有維護隊列的開銷,沒有上下文切換的開銷,可能導致不公平,但是性能比fair好很多。

ReentrantLock的帶參構造函數ReentrantLock(boolean fair)可以指定實現公平還是非公平鎖。默認是非公平鎖。

4.4 閉鎖 Latch

閉鎖(Latch)是一種同步工具類,可以延遲線程的進度直到其到達終止狀態。
閉鎖的作用相當于一扇門:在閉鎖到達結束狀態之前,這扇門一直是關閉的,并且沒有任何線程能通過,當到達結束狀態時,這扇門會打開并允許所有的線程通過。

Java中CountDownLatch是一種閉鎖實現,位于concurrent包下。

4.5 鎖消除

鎖消除指的是在JVM即使編譯時,通過運行少下文的掃描,去除不可能存在共享資源競爭的鎖。
通過鎖消除,可以節省毫無意義的鎖請求.

比如在單線程下使用StringBuffer,其中的同步完全沒有必要,這時候JVM可以在運行時基于逃逸分析計數,消除不必要的鎖。

五、如何避免死鎖

死鎖是類似這樣的情況:a,b兩個線程,a持有鎖A 等待鎖B;b持有鎖B等待鎖A。a,b相互等待,誰也執行不下去。
避免死鎖的原則是

  1. 避免持有多個鎖。
  2. 如果確實需要多個鎖,所有代碼都應該按照相同的順序去申請鎖。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容