前言
鎖機制無處不在,鎖機制是實作線程同步的基礎,鎖機制并不是Java鎖獨有的,其他各種計算機語言中也有着鎖機制相關的實作,資料庫中也有鎖的相關内容,希望可以幫助大家從Java入手,深入學習、了解Java中的鎖機制,提升Java并發程式設計能力。
1、樂觀鎖
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5iM2QTMxUTY2UWN5IDNmVTYyYzXzUTMxIjMyEzLcZDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL0M3Lc9CX6MHc0RHaiojIsJye.jpg)
樂觀鎖是一種樂觀思想,假定目前環境是讀多寫少,遇到并發寫的機率比較低,讀資料時認為别的線程不會正在進行修改(是以沒有上鎖)。寫資料時,判斷目前 與期望值是否相同,如果相同則進行更新(更新期間加鎖,保證是原子性的)。
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 可以綁定多個條件
筆記已經打包好了~