用最通俗的語言解釋Synchronized原理以及偏向鎖,自旋鎖,輕量級鎖和重量級鎖的區別(附面試中涉及到的鎖的解釋)

在早以前的版本中,synchronized一直被冠以性能消耗高,十分重的標籤,而且給他取名爲重量級鎖,不過在jdk1.6後對synchronized進行了一波優化,使他變得並無那麼重了,以致於如今咱們可使用synchronized而不用特別擔憂它的性能消耗問題微信

synchronized鎖的優化

首先咱們作一個最簡單的比喻,咱們把被鎖鎖住的代碼比喻爲一個房間,房間的鑰匙只有一把, 每個進入代碼塊的線程表示進入這個屋子的人,這個房間有一個管理員,從管理員那邊要鑰匙的過程,就是獲取鎖的過程.併發

偏向鎖

在第一我的A進入房間之後,管理員沒有把鑰匙收起來,而是把鑰匙放在門框上,而且告訴這我的,之後只要是你來直接開門就好,這樣若是一直這個A使用這個房間,那麼他就不須要每次都向管理員拿鑰匙,直接去門框上取鑰匙就行了,這就是偏向鎖.性能

偏向鎖,顧名思義是偏向某一個線程的鎖,jdk的開發人員發現,雖然使用人員對這塊代碼加了鎖,可是大部分時間仍是單獨的一個線程來訪問,若是此時每次還要手動獲取鎖,那麼對性能的消耗也是極大的.優化

因此在該某個線程第一次成功鎖時,會在對象頭和線程的棧幀中的鎖記錄中存儲所偏向的線程id,若是下一次仍是該線程獲取鎖,則不須要進行cas操做來加鎖和解鎖,大大減小了性能的消耗.線程

偏向鎖只能在有且只有一個線程獲取鎖時生效,若是有第二個線程想要獲取鎖,那麼偏向鎖就會膨脹爲輕量級鎖cdn

輕量級鎖

咱們繼續使用上面的比喻,若是此時來了一個B也要進入該房間,他發現該房間已經被開過了,那麼他就去門框上找鑰匙,若是找到了鑰匙,那麼他也不須要去找管理員,直接用鑰匙開門就好,也省下了跟管理員交互的開銷.對象

輕量級鎖是相對於重量級鎖而言,輕量級鎖不須要申請互斥量,只須要將markwork中的部分字節CAS更新指向線程的id,若是更新成功則表示已經成功的獲取了鎖,不然說明已經有線程獲取了輕量級鎖,發生了鎖競爭,輕量級鎖開始自旋.blog

自旋鎖與自適應自旋鎖

比喻繼續,在B使用房間期間,A也來了想進入房間,他來了之後發現鑰匙不在門框上,此時他沒有直接去找管理員,而是在門口轉悠了幾圈,若是此時B使用完了房間,那麼A則能夠繼續使用,若是B好半天還不出來,或者此時又來了一個C,那麼A就去找管理員排隊(膨脹爲重量級鎖)繼承

自旋鎖是輕量級鎖在鎖膨脹前作的最後一次掙扎,由於有的代碼即使加了鎖,可是執行效率很快,或者競爭率很低,競爭的線程沒有必要阻塞掉,只須要自我循環(例如for循環)等待很短的時間,上一個線程就把鎖釋放了,在1.5中jdk設置自旋鎖爲自旋十次,在1.6中優化爲自適應的自旋鎖,能夠根據加鎖的代碼來決定要自選幾回. 若是自旋超過必定次數,或者此時有第三個線程來競爭該鎖時,鎖膨脹爲重量級鎖遞歸

重量級鎖

synchronized的具體實現以下圖

線程競爭
Jvm每次從隊列中取出一個線程來用於鎖競爭候選者即競爭線程.可是併發狀況下,尾部list會被大量的併發線程的訪問爲了下降競爭,提升獲取線程的速度,JVM將競爭的list拆爲了兩份,獲取競爭線程時只從頭部獲取,而新進入的競爭線程則被放到尾部.提升了競爭時的效率.當Owner線程在unlock時會將尾部線程的部分線程遷移到頭部線程中,而且制定頭部線程的某一個線程做爲競爭線程,可是並無直接將鎖交給競爭線程,而是讓競爭線程本身來獲取鎖,這樣作雖然會犧牲公平性,可是會極大的提高系統的吞吐量.

synchronized是非公平鎖.當線程在進入尾部隊列以前,會嘗試着先自旋獲取鎖,若是獲取失敗才選擇進入尾部隊列.

其餘鎖名詞的解釋

公平鎖和非公平鎖

顧名思義 公平鎖即爲先進隊列的先獲取鎖,十分公平,而非公平鎖則像synchronized同樣頗有可能不進入隊列,直接獲取鎖,插隊進入.

樂觀鎖與悲觀鎖

樂觀鎖是一種樂觀思想,認爲讀多寫少,遇到併發寫的可能性比較低,每次獲取數據不回家所,在更新時根據版本號判斷此間是否有別人更改過數據,若是有人更改了數據,則重複讀--比較--寫的操做

悲觀鎖則是悲觀思想 認爲寫多讀少 每次拿數據都會上鎖,其餘讀的數據都會阻塞知道拿到鎖

可重入鎖

可重入鎖又名遞歸鎖,是指同一個線程在外層方法獲取鎖的時候,在進入該線程的內層方法會自動獲取鎖(前提是鎖的是同一個對象或者class),不會由於以前已經獲取過尚未釋放鎖而阻塞,Java中synchronized和ReentrantLock都是可重入鎖在某些狀況下能夠避免死鎖

那麼可重入鎖是怎麼實現的呢,首先可重入鎖和非可重入鎖都繼承父類AQS,AQS具體實現會單開一章來說,在其父類中維護一個同步狀態status來計數衝入次數,status初始爲0,當線程獲取鎖時則status+1

可重入鎖與非可重入鎖的區別在於,可重入鎖在status!=0時會檢測該線程是否獲取過該對象的鎖,若是獲取過則將status再+1,釋放是也是一層層的釋放直到status=0

而非可重入鎖不論是誰獲取的鎖,只要該status不等於0 那麼不可重入鎖就會阻塞,若是是該線程本身獲取了該鎖而且沒有超時時間的活,則構成了死鎖

獨佔鎖與共享鎖

獨佔鎖是指該鎖一次只能被一個線程來使用,共享鎖則能夠被多個線程來持有.

互斥鎖與讀寫鎖

上面說的獨佔鎖與共享鎖是一種廣義的概念,而互斥鎖與讀寫鎖則是具體的實現

在Java中互斥鎖的具體實現爲ReentrantLock,而讀寫鎖的具體時間爲ReadWriteLock

互斥鎖任什麼時候刻只有一個線程能夠獲取,而讀寫鎖在讀讀的時候能夠併發執行,而讀寫,寫寫則是互斥的

感謝閱讀

有興趣能夠關注個人我的微信公衆號,會按期推送關於Java的技術文章,並且目前不恰飯都是乾貨哈哈哈哈

小阿宅Java
相關文章
相關標籤/搜索