5000字 | 24張圖帶你徹底理解21種並發鎖
- 2020 年 9 月 3 日
- 筆記
- 26-Java並發, 92-JAVA, JAVA, Lock, ReentrantLock, synchronized, 可重入鎖, 自旋鎖, 鎖優化
本篇主要內容如下:
我的SpringCloud實戰項目持續更新中
幫你總結好的鎖:
序號 | 鎖名稱 | 應用 |
---|---|---|
1 | 樂觀鎖 | CAS |
2 | 悲觀鎖 | synchronized、vector、hashtable |
3 | 自旋鎖 | CAS |
4 | 可重入鎖 | synchronized、Reentrantlock、Lock |
5 | 讀寫鎖 | ReentrantReadWriteLock,CopyOnWriteArrayList、CopyOnWriteArraySet |
6 | 公平鎖 | Reentrantlock(true) |
7 | 非公平鎖 | synchronized、reentrantlock(false) |
8 | 共享鎖 | ReentrantReadWriteLock中讀鎖 |
9 | 獨佔鎖 | synchronized、vector、hashtable、ReentrantReadWriteLock中寫鎖 |
10 | 重量級鎖 | synchronized |
11 | 輕量級鎖 | 鎖優化技術 |
12 | 偏向鎖 | 鎖優化技術 |
13 | 分段鎖 | concurrentHashMap |
14 | 互斥鎖 | synchronized |
15 | 同步鎖 | synchronized |
16 | 死鎖 | 相互請求對方的資源 |
17 | 鎖粗化 | 鎖優化技術 |
18 | 鎖消除 | 鎖優化技術 |
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 自己控制的。
讀鎖: 允許多個執行緒獲取讀鎖,同時訪問同一個資源。
寫鎖: 只允許一個執行緒獲取寫鎖,不允許同時訪問同一個資源。
如何使用:
/**
* 創建一個讀寫鎖
* 它是一個讀寫融為一體的鎖,在使用的時候,需要轉換
*/
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
獲取讀鎖和釋放讀鎖
// 獲取讀鎖
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 可以綁定多個條件
彩蛋: 講了那麼多鎖,都跟阻塞相關,寶寶想聽阻塞呀!
我是悟空,一隻努力變強的碼農!我要變身超級賽亞人啦!
你好,我是
悟空哥
,7年項目開發經驗,全棧工程師,開發組長,超喜歡圖解編程底層原理。正在編寫兩本PDF,分別是 1、Spring Cloud實戰項目(佳必過),2、Java並發必知必會。我還手寫了2個小程式
,Java刷題小程式,PMP刷題小程式,點擊我的公眾號菜單打開!另外有111本架構師資料以及1000道Java面試題,都整理成了PDF,可以關注公眾號 悟空聊架構 回復悟空
領取優質資料。
轉發->在看->點贊->收藏->評論!!!是對我最大的支援!
《Java並發必知必會》系列:
1.反制面試官 | 14張原理圖 | 再也不怕被問 volatile!