系統梳理一下鎖


背景

有人對Java主流鎖做了下面全面的梳理。梳理的確實挺好的。但是我看到這張圖,第一個感覺是:記不住。

  

因為分了太多類,彼此之間沒有什么聯系。做PPT可以。如果聊天或者面試,不用紙筆的情況下,就不太好描述了。也不利於對原理和應用的理解。

基於上述的考慮,我就自己系統的梳理一下鎖,希望可以有助於大家理解和記憶,以至於最后在工作中得到很好的應用。

先說線程鎖再說分布式鎖。

 

線程鎖

概述 

這里說的線程鎖是Java線程鎖,從原理上各個語言應該都比較相似。有很多維度的划分方式,我比較建議的是從大面上分為樂觀鎖和悲觀鎖。

樂觀鎖主要是自旋+CAS的方式,比如JUC(java.util.concurrent包)的原子類。 

悲觀鎖主要用synchronized關鍵字的隱式鎖和基於AQS的顯示鎖。

上面三段總結如下:

 

悲觀鎖的實現原理

1>synchronized關鍵字

隨着java版本升級,synchronized關鍵字雖然是用C++寫的,但是原理和JCU包的ReentrantLock很相似。synchronized關鍵字有4種鎖狀態:無鎖、偏向鎖、輕量級鎖、重量級鎖。無鎖類似於ReentrantLock的交替執行,沒有並發,就不涉及鎖;偏向鎖類似於ReentrantLock的可重入的概念,使得已經獲取到鎖的線程可以多次獲取鎖;輕量級鎖解決的問題是盡量避免線程切換,使用的方法也和ReentrantLock相似,是自旋+CAS的方式;重量級鎖依賴於管程monitor來實現,和ReentrantLock一樣都涉及用戶態和內核態切換。

根據這個我們再來補充一下Java線程鎖的思維導圖:

 

2>基於基於AQS的顯示鎖

基於AQS的顯示鎖我之前看過一些源碼。這里面比較經典的是ReentrantLock。這是可重入鎖,就是同一個線程可以反復進入加鎖的線程。如果想實現不可重入鎖也很簡單。把可重入鎖對當前線程做特殊處理的部分去掉就好了。

其他JCU下locks包里的鎖比如讀寫鎖就是將鎖細化成了讀鎖和寫鎖。讀鎖是共享鎖的實現,寫鎖是排他鎖的實現。

ReentrantLock可以使用公平鎖和非公平鎖兩種方式,公平鎖和非公平鎖各自繼承了AQS。區別只是非公平鎖在需要加鎖時先直接嘗試是否可以獲取鎖成功,而公平鎖是先看自己是否需要排隊。

下面以ReentrantLock的公平鎖為例來簡單聊一下AQS的源碼。AQS核心是實現了CLH隊列。

AQS有head、tail、持有鎖的線程、狀態4個主要的成員變量。

利用head!=tail就是說AQS是否未被初始化來判斷是否交替執行,交替執行則不用加鎖;如果需要加鎖則判斷是否就是當前擁有鎖的線程,是的話,將進入次數+1;如果不是則判斷是否需要初始化AQS,需要的話先初始化一個dummy header,再將自己加入隊尾,如果是隊列里dummy header的指針指向的節點,則它為先自旋判斷是否可以獲取鎖;如果不是dummy header指針指向的節點,則使用park讓出cpu。當dummy header的指針指向的節點獲取到鎖之后,會將head指向自己,同時將自己這個Node節點的當前線程設置為空,將自己設置為dummy header,同時將原來dummy header的指針都設置為null,使得原dummy header成為一個沒有引用的節點,便於垃圾回收。

根據這個我們再來補充一下Java線程鎖的思維導圖:

 

 

 

 

分布式鎖

不管是線程鎖還是分布式鎖,都實現了tryLock、lock、unlock三個方法。

tryLock的語義是非阻塞鎖,嘗試獲取鎖,成功返回true,不成功返回false;主流lock語義是阻塞鎖。實現一般基於tryLock來做自旋,不成功的時候也會有像ReentrantLock一樣的阻塞操作。

常見的分布式鎖實現以及數據庫鎖的實現詳見之前寫的文章:《MySQL常見6個考題在實際工作中的運用》這里就不再贅述了。

 

總結

本篇文章在介紹知識點是次要的,主要是展示了總結思考的思路,希望能對讀者朋友們的思考問題方法上有所幫助,僅做參考。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM