背景java
有人對Java主流鎖作了下面全面的梳理。梳理的確實挺好的。可是我看到這張圖,第一個感受是:記不住。面試
由於分了太多類,彼此之間沒有什麼聯繫。作PPT能夠。若是聊天或者面試,不用紙筆的狀況下,就不太好描述了。也不利於對原理和應用的理解。數據庫
基於上述的考慮,我就本身系統的梳理一下鎖,但願能夠有助於你們理解和記憶,以致於最後在工做中獲得很好的應用。併發
先說線程鎖再說分佈式鎖。分佈式
線程鎖線程
概述 指針
這裏說的線程鎖是Java線程鎖,從原理上各個語言應該都比較類似。有不少維度的劃分方式,我比較建議的是從大面上分爲樂觀鎖和悲觀鎖。blog
樂觀鎖主要是自旋+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個考題在實際工做中的運用》這裏就再也不贅述了。
總結
本篇文章在介紹知識點是次要的,主要是展現了總結思考的思路,但願能對讀者朋友們的思考問題方法上有所幫助,僅作參考。