synchronized 內部原理、常見鎖策略、CAS、 以及死鎖的產生和解決
目錄
1、synchronized 內部原理
物件頭鎖狀態:
(1)無鎖
(2)偏向鎖
(3)輕量級鎖
(4)重量級鎖
重量級鎖:通過物件內部的監視器(monitor)實現,而其中 monitor 的本質是依賴於底層作業系統的 Mutex 鎖實現
synchronized 內部monitor機制:
1.進入synchronized 程式碼行,編譯為monitorenter位元組碼,其他執行緒不能進入
2.退出synchronized 程式碼行,編譯為monitorexit位元組碼命令。
3.synchronized 使用計數器判斷獲取物件鎖的次數,可重入性的表現。
缺點: 1.當有一個執行緒獲取鎖之後,其餘所有等待獲取該鎖的執行緒都會處於阻塞狀態。
2.作業系統實現執行緒之間的切換需要從使用者態切換到核心態,切換成本非常高。
注:synchronized 鎖只能升級 不能降級。
無鎖---->偏向鎖 ---->輕量級鎖---->重量級鎖
1.1、Synchronized鎖優化
鎖粗化:鎖粗化就是將多次連線在一起的加鎖、解鎖操作合併為一次,將多個連續的鎖擴充套件成為一個範圍更大的鎖。舉例如下:
public class Test{
private static StringBuffer sb = new StringBuffer();
public static void main(String[]args){
sb.append("a");
sb.append("b");
sb.append("c");
}
}
這裡每次呼叫stringBuffer.append方法都需要加鎖和解鎖,如果虛擬機器檢測到有一系列連串的對同一個物件加鎖和解鎖操作,就會將其合併成一次範圍更大的加鎖和解鎖操作,即在第一次append方法時進行加鎖,最後一次append方法結束後進行解鎖。
鎖消除:鎖消除即刪除不必要的加鎖操作。根據程式碼逃逸技術,如果判斷到一段程式碼中,堆上的資料不會逃逸出當前執行緒,那 麼可以認為這段程式碼是執行緒安全的,不必要加鎖。看下面這段程式:
public class Test{
public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
sb.append("a").append("b").append("c");
}
}
雖然StringBuffer的append是一個同步方法,但是這段程式中的StringBuffer屬於一個區域性變數,並且不會從該方法中 逃逸出去,所以其實這過程是執行緒安全的,可以將鎖消除。
2、常見的鎖策略
2.1、樂觀鎖 vs 悲觀鎖
樂觀鎖:樂觀鎖假設認為資料一般情況下不會產生併發衝突,所以在資料進行提交更新的時候,才會正式對資料是否產生併發衝突進行檢測,如果發現併發衝突了,則讓返回使用者錯誤的資訊,讓使用者決定如何去做。
悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。
悲觀鎖的問題:總是需要競爭鎖,進而導致發生執行緒切換,掛起其他執行緒;所以效能不高。
樂觀鎖的問題:並不總是能處理所有問題,所以會引入一定的系統複雜度。 執行時間很快,執行緒都是處於執行態嘗試修改值的操作。或是很多執行緒一直執行修改值的操作,效率比較低。
2.2、讀寫鎖 (readers-writer lock)
多執行緒之間,資料的讀取方之間不會產生執行緒安全問題,但資料的寫入方互相之間以及和讀者之間都需要進行互斥。如果兩種場景下都用同一個鎖,就會產生極大的效能損耗。所以讀寫鎖因此而產生。
讀寫鎖(readers-writer lock),看英文可以顧名思義,在執行加鎖操作時需要額外表明讀寫意圖,複數讀者之間並不互斥,而寫者則要求與任何人互斥。
2.3、自旋鎖(Spin Lock)
按之前的方式處理下,執行緒在搶鎖失敗後進入阻塞狀態,放棄 CPU,需要過很久才能再次被排程。但經過測算,實際的生活中,大部分情況下,雖然當前搶鎖失敗,但過不了很久,鎖就會被釋放。基於這個事實,自旋鎖誕生了。 你可以簡單的認為自旋鎖就是下面的程式碼
while (搶鎖(lock) == 失敗) {}
只要沒搶到鎖,就死等。
自旋鎖的缺點: 缺點其實非常明顯,就是如果之前的假設(鎖很快會被釋放)沒有滿足,則執行緒其實是光在消耗 CPU 資源,長期在做無用功的。
2.4、可重入鎖
可重入鎖的字面意思是“可以重新進入的鎖”,即允許同一個執行緒多次獲取同一把鎖。
比如一個遞迴函式裡有加鎖操作,遞迴過程中這個鎖會阻塞自己嗎?如果不會,那麼這個鎖就是可重入鎖(因為這個原因可重入鎖也叫做遞迴鎖)。 Java裡只要以Reentrant開頭命名的鎖都是可重入鎖,而且JDK提供的所有現成的Lock實現類,包括synchronized 關鍵字鎖都是可重入的。
3、CAS
CAS: 全稱Compare and swap,字面意思:”比較並交換“,
- 前提條件:原始值、預期值、修改值、版本號
- CAS實現:自旋嘗試設定值操作
- 自旋的實現:
(1)迴圈死等
(2)可中斷的方式:interrupt
(3)判斷迴圈次數,達到閾值退出
(4)判斷迴圈總耗時,達到閾值退出
一個 CAS 涉及到以下操作: 我們假設記憶體中的原資料V,舊的預期值A,需要修改的新值B。
- 比較 A 與 V 是否相等。(比較)
- 如果比較相等,將 B 寫入 V。(交換)
- 返回操作是否成功。
當多個執行緒同時對某個資源進行CAS操作,只能有一個執行緒操作成功,但是並不會阻塞其他執行緒,其他執行緒只會收到 操作失敗的訊號。可見 CAS 其實是一個樂觀鎖。
缺點:(1)如果之前的前提(鎖很快會被釋放)沒有滿足,執行緒一直處於執行態迴圈執行CAS,光在消耗 CPU 資源,長期在做無用功。
(2) 執行緒數量較多,前提可能滿足不了或者CPU在多執行緒間切換---->效能消耗大
1.為什麼引入CAS?
執行時間很快的程式碼,需要同步的時候,使用synchronized 方式效率會比較低。synchronized執行時間比較長的同步,效率還算比較好
2.ABA 問題如何處理
ABA 的問題,就是一個值從A變成了B又變成了A,預期是滿足要求的, 但這個期間我們不清楚這個過程,可能是其他執行緒修改過的。
解決方法:加入版本資訊,每次修改後,版本號加1,保證不會出現老的值
4、死鎖
前提:
同步的本質在於:一個執行緒等待另外一個執行緒執行完畢後才可以繼續執行。但是如果現在相關的幾個執行緒彼此之間都在等待著,那麼就會造成死鎖。
至少有兩個執行緒,互相持有對方申請的物件鎖,造成互相等待,導致沒法繼續執行
4.1、死鎖產生的四個必要條件:
1、互斥:程式要求對所分配的資源進行排它性控制,即在一段時間內某資源僅為一程式所佔用。
2、不可搶佔:資源請求者不能強制從資源佔有者手中奪取資源,資源只能由資源佔有者主動釋放。
3、請求和保持:程式已經保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他程式佔有,此時請求程式被阻塞,但對自己已獲得的資源保持不放。
4、環路等待,即存在一個等待佇列:P1佔有P2的資源,P2佔有P3的資源,P3佔有P1的資源。這樣就形成了 一個等待環路。
當上述四個條件都成立的時候,便形成死鎖。
4.2、解決死鎖的基本方法
4.2.1、預防
1.資源一次性分配:一次性分配所有資源,這樣就不會再有請求了:(破壞請求條件)
2.只要有一個資源得不到分配,也不給這個程式分配其他的資源:(破壞請保持條件)
3.可剝奪資源:即當某程式獲得了部分資源,但得不到其它資源,則釋放已佔有的資源(破壞不可剝奪條件)
4.資源有序分配法:系統給每類資源賦予一個編號,每一個程式按編號遞增的順序請求資源,釋放則相反(破壞環路等待條件)
4.2.2、避免
預防死鎖的幾種策略,會嚴重地損害系統效能。因此在避免死鎖時,要施加較弱的限制,從而獲得 較滿意的系統效能。由於在避免死鎖的策略中,允許程式動態地申請資源。因而,系統在進行資源分配之前預先計算資源分配的安全性。若此次分配不會導致系統進入不安全的狀態,則將資源分配給程式;否則,程式等待。其中最具有代表性的避免死鎖演算法是銀行家演算法。
銀行家演算法:首先需要定義狀態和安全狀態的概念。系統的狀態是當前給程式分配的資源情況。因此,狀態包含兩個向量Resource(系統中每種資源的總量)和Available(未分配給程式的每種資源的總量)及兩個矩陣Claim(表示程式對資源的需求)和Allocation(表示當前分配給程式的資源)。安全狀態是指至少有一個資源分配序列不會導致死鎖。當程式請求一組資源時,假設同意該請求,從而改變了系統的狀態,然後確定其結果是否還處於安全狀態。如果是,同意這個請求;如果不是,阻塞該程式知道同意該請求後系統狀態仍然是安全的。
4.2.3、檢測
檢測方式:
1.Jstack命令
2.JConsole工具
4.2.4、解除
當發現有程式死鎖後,便應立即把它從死鎖狀態中解脫出來,常採用的方法有:
1.剝奪資源:從其它程式剝奪足夠數量的資源給死鎖程式,以解除死鎖狀態;
2.撤消程式:可以直接撤消死鎖程式或撤消代價最小的程式,直至有足夠的資源可用,死鎖狀態.消除為止;所謂代價是指優先順序、執行代價、程式的重要性和價值等
相關文章
- 作業系統(5) 死鎖的概念 死鎖產生的必要條件 死鎖的處理策略 預防死鎖 避免死鎖 死鎖的檢測和解除 銀行家演算法作業系統演算法
- java多執行緒:執行緒同步synchronized(不同步的問題、佇列與鎖),死鎖的產生和解決Java執行緒synchronized佇列
- 剖析6個MySQL死鎖案例的原因以及死鎖預防策略MySql
- 常見的死鎖情況及解決方法
- synchronized 鎖的原理synchronized
- MySQL死鎖系列-常見加鎖場景分析MySql
- 詳解鎖原理,synchronized、volatile+cas底層實現synchronized
- java裡的鎖總結(synchronized隱式鎖、Lock顯式鎖、volatile、CAS)Javasynchronized
- (資料庫十)資料庫中的鎖機制以及死鎖產生的原因及解決辦法資料庫
- 【Oracle】死鎖的產生與處理Oracle
- 常見MongoDB資料庫操作產生的鎖總結MongoDB資料庫
- 基礎篇:詳解鎖原理,volatile+cas、synchronized的底層實現synchronized
- Java中的鎖原理、鎖優化、CAS、AQS詳解!Java優化AQS
- PostgreSQL 死鎖異常SQL
- 面試:什麼是死鎖,如何避免或解決死鎖;MySQL中的死鎖現象,MySQL死鎖如何解決面試MySql
- 什麼是死鎖?如何解決死鎖?
- MySQL併發時經典常見的死鎖原因及解決方法MySql
- 死鎖問題排查過程-間隙鎖的復現以及解決
- 悲觀的併發策略——synchronized互斥鎖synchronized
- Java中常見死鎖與活鎖的例項Java
- 死磕Synchronized底層實現–偏向鎖synchronized
- ORA-00060: Deadlock detected 模擬死鎖產生與解決方案
- MySQL 死鎖解決MySql
- MySQL解決死鎖MySql
- 死磕Synchronized底層實現--重量級鎖synchronized
- synchronized實現原理及鎖優化synchronized優化
- synchronized類鎖與物件鎖synchronized物件
- synchronized的實現原理——鎖膨脹過程synchronized
- 死磕Synchronized底層實現--輕量級鎖synchronized
- 死磕Synchronized底層實現–輕量級鎖synchronized
- 關於 ReentrantLock 中鎖 lock() 和解鎖 unlock() 的底層原理淺析ReentrantLock
- 我們常說的 CAS 自旋鎖是什麼
- Synchronized同步鎖synchronized
- 淺談Java中的鎖:Synchronized、重入鎖、讀寫鎖Javasynchronized
- 深刨顯式鎖ReentrantLock原理及其與內建鎖的區別,以及讀寫鎖ReentrantReadWriteLock使用場景ReentrantLock
- redis分散式鎖的問題和解決Redis分散式
- 鎖的使用與死鎖的避免
- 死鎖