1. 程式人生 > 其它 >24張圖 | 帶你徹底理解Java中的21種鎖

24張圖 | 帶你徹底理解Java中的21種鎖

1、樂觀鎖

樂觀鎖是一種樂觀思想,假定當前環境是讀多寫少,遇到併發寫的概率比較低,讀資料時認為別的執行緒不會正在進行修改(所以沒有上鎖)。寫資料時,判斷當前 與期望值是否相同,如果相同則進行更新(更新期間加鎖,保證是原子性的)。

Java中的樂觀鎖CAS,比較並替換,比較當前值(主記憶體中的值),與預期值(當前執行緒中的值,主記憶體中值的一份拷貝)是否一樣,一樣則更新,否則繼續進行CAS操作。

如上圖所示,可以同時進行讀操作,讀的時候其他執行緒不能進行寫操作。

2、悲觀鎖

悲觀鎖是一種悲觀思想,即認為寫多讀少,遇到併發寫的可能性高,每次去拿資料的時候都認為其他執行緒會修改,所以每次讀寫資料都會認為其他執行緒會修改,所以每次讀寫資料時都會上鎖。其他執行緒想要讀寫這個資料時,會被這個執行緒block,直到這個執行緒釋放鎖然後其他執行緒獲取到鎖。

Java中的悲觀鎖synchronized修飾的方法和方法塊、ReentrantLock

如上圖所示,只能有一個執行緒進行讀操作或者寫操作,其他執行緒的讀寫操作均不能進行。

3、自旋鎖

自旋鎖是一種技術:為了讓執行緒等待,我們只須讓執行緒執行一個忙迴圈(自旋)。

現在絕大多數的個人電腦和伺服器都是多路(核)處理器系統,如果物理機器有一個以上的處理器或者處理器核心,能讓兩個或以上的執行緒同時並行執行,就可以讓後面請求鎖的那個執行緒“稍等一會”,但不放棄處理器的執行時間,看看持有鎖的執行緒是否很快就會釋放鎖。

自旋鎖的優點:避免了執行緒切換的開銷。掛起執行緒和恢復執行緒的操作都需要轉入核心態中完成,這些操作給Java虛擬機器的併發效能帶來了很大的壓力。

自旋鎖的缺點:佔用處理器的時間,如果佔用的時間很長,會白白消耗處理器資源,而不會做任何有價值的工作,帶來效能的浪費。因此自旋等待的時間必須有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去掛起執行緒。

自旋次數預設值:10次,可以使用引數-XX:PreBlockSpin來自行更改。

自適應自旋自適應意味著自旋的時間不再是固定的,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定的。有了自適應自旋,隨著程式執行時間的增長及效能監控資訊的不斷完善,虛擬機器對程式鎖的狀態預測就會越來越精準。

Java中的自旋鎖CAS操作中的比較操作失敗後的自旋等待。

4、可重入鎖(遞迴鎖)

可重入鎖是一種技術:任意執行緒在獲取到鎖之後能夠再次獲取該鎖而不會被鎖所阻塞。

可重入鎖的原理:通過組合自定義同步器來實現鎖的獲取與釋放。

  • 再次獲取鎖:識別獲取鎖的執行緒是否為當前佔據鎖的執行緒,如果是,則再次成功獲取。獲取鎖後,進行計數自增,
  • 釋放鎖:釋放鎖時,進行計數自減。

Java中的可重入鎖ReentrantLock、synchronized修飾的方法或程式碼段。

可重入鎖的作用:避免死鎖。

面試題1:可重入鎖如果加了兩把,但是隻釋放了一把會出現什麼問題?

答:程式卡死,執行緒不能出來,也就是說我們申請了幾把鎖,就需要釋放幾把鎖。

面試題2:如果只加了一把鎖,釋放兩次會出現什麼問題?

答:會報錯,java.lang.IllegalMonitorStateException。

5、讀寫鎖

讀寫鎖是一種技術:通過ReentrantReadWriteLock類來實現。為了提高效能, Java 提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,如果沒有寫鎖的情況下,讀是無阻塞的,在一定程度上提高了程式的執行效率。讀寫鎖分為讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥,這是由 jvm 自己控制的。

讀鎖:允許多個執行緒獲取讀鎖,同時訪問同一個資源。

寫鎖:只允許一個執行緒獲取寫鎖,不允許同時訪問同一個資源。

如何使用:

/**
*建立一個讀寫鎖
*它是一個讀寫融為一體的鎖,在使用的時候,需要轉換
*/
privateReentrantReadWriteLockrwLock=newReentrantReadWriteLock();

獲取讀鎖和釋放讀鎖

//獲取讀鎖
rwLock.readLock().lock();

//釋放讀鎖
rwLock.readLock().unlock();

獲取寫鎖和釋放寫鎖

//建立一個寫鎖
rwLock.writeLock().lock();

//寫鎖釋放
rwLock.writeLock().unlock();

Java中的讀寫鎖:ReentrantReadWriteLock

6、公平鎖

公平鎖是一種思想:多個執行緒按照申請鎖的順序來獲取鎖。在併發環境中,每個執行緒會先檢視此鎖維護的等待佇列,如果當前等待佇列為空,則佔有鎖,如果等待佇列不為空,則加入到等待佇列的末尾,按照FIFO的原則從佇列中拿到執行緒,然後佔有鎖。

7、非公平鎖

非公平鎖是一種思想:執行緒嘗試獲取鎖,如果獲取不到,則再採用公平鎖的方式。多個執行緒獲取鎖的順序,不是按照先到先得的順序,有可能後申請鎖的執行緒比先申請的執行緒優先獲取鎖。

優點:非公平鎖的效能高於公平鎖。

缺點:有可能造成執行緒飢餓(某個執行緒很長一段時間獲取不到鎖)

Java中的非公平鎖:synchronized是非公平鎖,ReentrantLock通過建構函式指定該鎖是公平的還是非公平的,預設是非公平的。

8、共享鎖

共享鎖是一種思想:可以有多個執行緒獲取讀鎖,以共享的方式持有鎖。和樂觀鎖、讀寫鎖同義。

Java中用到的共享鎖:ReentrantReadWriteLock

9、獨佔鎖

獨佔鎖是一種思想:只能有一個執行緒獲取鎖,以獨佔的方式持有鎖。和悲觀鎖、互斥鎖同義。

Java中用到的獨佔鎖:synchronized,ReentrantLock

10、重量級鎖

重量級鎖是一種稱謂:synchronized是通過物件內部的一個叫做監視器鎖(monitor)來實現的,監視器鎖本身依賴底層的作業系統的Mutex Lock來實現。作業系統實現執行緒的切換需要從使用者態切換到核心態,成本非常高。這種依賴於作業系統Mutex Lock來實現的鎖稱為重量級鎖。為了優化synchonized,引入了輕量級鎖偏向鎖

Java中的重量級鎖:synchronized

11、輕量級鎖

輕量級鎖是JDK6時加入的一種鎖優化機制:輕量級鎖是在無競爭的情況下使用CAS操作去消除同步使用的互斥量。輕量級是相對於使用作業系統互斥量來實現的重量級鎖而言的。輕量級鎖在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的效能消耗。如果出現兩條以上的執行緒爭用同一個鎖的情況,那輕量級鎖將不會有效,必須膨脹為重量級鎖。

優點:如果沒有競爭,通過CAS操作成功避免了使用互斥量的開銷。

缺點:如果存在競爭,除了互斥量本身的開銷外,還額外產生了CAS操作的開銷,因此在有競爭的情況下,輕量級鎖比傳統的重量級鎖更慢。

12、偏向鎖

偏向鎖是JDK6時加入的一種鎖優化機制:在無競爭的情況下把整個同步都消除掉,連CAS操作都不去做了。偏是指偏心,它的意思是這個鎖會偏向於第一個獲得它的執行緒,如果在接下來的執行過程中,該鎖一直沒有被其他的執行緒獲取,則持有偏向鎖的執行緒將永遠不需要再進行同步。持有偏向鎖的執行緒以後每次進入這個鎖相關的同步塊時,虛擬機器都可以不再進行任何同步操作(例如加鎖、解鎖及對Mark Word的更新操作等)。

優點:把整個同步都消除掉,連CAS操作都不去做了,優於輕量級鎖。

缺點:如果程式中大多數的鎖都總是被多個不同的執行緒訪問,那偏向鎖就是多餘的。

13、分段鎖

分段鎖是一種機制:最好的例子來說明分段鎖是ConcurrentHashMap。ConcurrentHashMap原理:它內部細分了若干個小的 HashMap,稱之為段(Segment)。預設情況下一個 ConcurrentHashMap 被進一步細分為 16 個段,既就是鎖的併發度。如果需要在 ConcurrentHashMap 新增一項key-value,並不是將整個 HashMap 加鎖,而是首先根據 hashcode 得到該key-value應該存放在哪個段中,然後對該段加鎖,並完成 put 操作。在多執行緒環境中,如果多個執行緒同時進行put操作,只要被加入的key-value不存放在同一個段中,則執行緒間可以做到真正的並行。

執行緒安全:ConcurrentHashMap 是一個 Segment 陣列, Segment 通過繼承ReentrantLock 來進行加鎖,所以每次需要加鎖的操作鎖住的是一個 segment,這樣只要保證每個 Segment 是執行緒安全的,也就實現了全域性的執行緒安全

14、互斥鎖

互斥鎖與悲觀鎖、獨佔鎖同義,表示某個資源只能被一個執行緒訪問,其他執行緒不能訪問。

  • 讀-讀互斥
  • 讀-寫互斥
  • 寫-讀互斥
  • 寫-寫互斥

Java中的同步鎖:synchronized

15、同步鎖

同步鎖與互斥鎖同義,表示併發執行的多個執行緒,在同一時間內只允許一個執行緒訪問共享資料。

Java中的同步鎖:synchronized

16、死鎖

死鎖是一種現象:如執行緒A持有資源x,執行緒B持有資源y,執行緒A等待執行緒B釋放資源y,執行緒B等待執行緒A釋放資源x,兩個執行緒都不釋放自己持有的資源,則兩個執行緒都獲取不到對方的資源,就會造成死鎖。

Java中的死鎖不能自行打破,所以執行緒死鎖後,執行緒不能進行響應。所以一定要注意程式的併發場景,避免造成死鎖。

17、鎖粗化

鎖粗化是一種優化技術:如果一系列的連續操作都對同一個物件反覆加鎖和解鎖,甚至加鎖操作都是出現在迴圈體體之中,就算真的沒有執行緒競爭,頻繁地進行互斥同步操作將會導致不必要的效能損耗,所以就採取了一種方案:把加鎖的範圍擴充套件(粗化)到整個操作序列的外部,這樣加鎖解鎖的頻率就會大大降低,從而減少了效能損耗。

18、鎖消除

鎖消除是一種優化技術:就是把鎖幹掉。當Java虛擬機器執行時發現有些共享資料不會被執行緒競爭時就可以進行鎖消除。

那如何判斷共享資料不會被執行緒競爭?

利用逃逸分析技術:分析物件的作用域,如果物件在A方法中定義後,被作為引數傳遞到B方法中,則稱為方法逃逸;如果被其他執行緒訪問,則稱為執行緒逃逸。

在堆上的某個資料不會逃逸出去被其他執行緒訪問到,就可以把它當作棧上資料對待,認為它是執行緒私有的,同步加鎖就不需要了。

19、synchronized

synchronized是Java中的關鍵字:用來修飾方法、物件例項。屬於獨佔鎖、悲觀鎖、可重入鎖、非公平鎖。

  • 1.作用於例項方法時,鎖住的是物件的例項(this);

  • 2.當作用於靜態方法時,鎖住的是 Class類,相當於類的一個全域性鎖, 會鎖所有呼叫該方法的執行緒;

  • 3.synchronized 作用於一個非 NULL的物件例項時,鎖住的是所有以該物件為鎖的程式碼塊。它有多個佇列,當多個執行緒一起訪問某個物件監視器的時候,物件監視器會將這些執行緒儲存在不同的容器中。

每個物件都有個 monitor 物件, 加鎖就是在競爭 monitor 物件,程式碼塊加鎖是在程式碼塊前後分別加上 monitorenter 和 monitorexit 指令來實現的,方法加鎖是通過一個標記位來判斷的。

20、Lock和synchronized的區別

自動擋和手動擋的區別

Lock是Java中的介面,可重入鎖、悲觀鎖、獨佔鎖、互斥鎖、同步鎖。

  • 1.Lock需要手動獲取鎖和釋放鎖。就好比自動擋和手動擋的區別
  • 2.Lock 是一個介面,而 synchronized 是 Java 中的關鍵字, synchronized 是內建的語言實現。
  • 3.synchronized 在發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生;而 Lock 在發生異常時,如果沒有主動通過 unLock()去釋放鎖,則很可能造成死鎖現象,因此使用 Lock 時需要在 finally 塊中釋放鎖。
  • 4.Lock 可以讓等待鎖的執行緒響應中斷,而 synchronized 卻不行,使用 synchronized 時,等待的執行緒會一直等待下去,不能夠響應中斷。
  • 5.通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
  • 6.Lock 可以通過實現讀寫鎖提高多個執行緒進行讀操作的效率。

synchronized的優勢:

  • 足夠清晰簡單,只需要基礎的同步功能時,用synchronized。
  • Lock應該確保在finally塊中釋放鎖。如果使用synchronized,JVM確保即使出現異常,鎖也能被自動釋放。
  • 使用Lock時,Java虛擬機器很難得知哪些鎖物件是由特定執行緒鎖持有的。

21、ReentrantLock 和synchronized的區別

ReentrantLock是Java中的類 :繼承了Lock類,可重入鎖、悲觀鎖、獨佔鎖、互斥鎖、同步鎖。

劃重點

相同點:

  • 1.主要解決共享變數如何安全訪問的問題
  • 2.都是可重入鎖,也叫做遞迴鎖,同一執行緒可以多次獲得同一個鎖,
  • 3.保證了執行緒安全的兩大特性:可見性、原子性。

不同點:

  • 1.ReentrantLock 就像手動汽車,需要顯示的呼叫lock和unlock方法, synchronized 隱式獲得釋放鎖。

  • 2.ReentrantLock 可響應中斷, synchronized 是不可以響應中斷的,ReentrantLock 為處理鎖的不可用性提供了更高的靈活性

  • 3.ReentrantLock 是 API 級別的, synchronized 是 JVM 級別的

  • 4.ReentrantLock 可以實現公平鎖、非公平鎖,預設非公平鎖,synchronized 是非公平鎖,且不可更改。

  • 5.ReentrantLock 通過 Condition 可以繫結多個條件