扯扯Java中的鎖

淡墨痕發表於2020-07-27

前言

    又過去了一個週末,最近陸陸續續的看了《併發程式設計的藝術》一書,對鎖有不少感悟,這次就聊聊Java中的鎖事。本文純粹是漫談,想到哪說到哪,但準確性肯定會保證,倘若有不正確之處,還請交流指正。

正文

    作為Java開發, 說到鎖第一時間想到的肯定是synchronized和juc包中的lock鎖這倆兄弟,但如果眼光放開點,會發現還有很多其他的鎖:叢集/分散式環境下的分散式鎖、mysql中的那一家子鎖、作業系統中基於訊號量/互斥量等構成的鎖。。。上面說的是鎖的應用場景,而說起鎖本身,定義的型別也不少,什麼悲觀鎖/樂觀鎖、獨佔鎖/共享鎖、讀寫鎖、公平鎖/非公平鎖、重入鎖、輕量級鎖/重量級鎖、自旋鎖、偏向鎖...

    鎖是幹什麼的?用個人的話來總結一下就是:用於控制不同訪問來源對同一資料的訪問順序。也就是說鎖的應用涉及到兩個關鍵條件:不同訪問來源(可以是不同執行緒、不同程式、不同APP)和操作同一資料(即共享的資料)。

    下面對鎖進行一下總結,首先按鎖的實現思想分類:

    悲觀鎖和樂觀鎖屬於鎖的實現思想,synchronized是悲觀鎖,而基於樂觀鎖思想的實現是以CAS為基礎構建的lock鎖,同樣mysql中的鎖屬於悲觀鎖,用redis或者zk構建的分散式鎖也都是悲觀鎖,作業系統中基於訊號量/互斥量等構建起來的鎖也都屬於悲觀鎖範疇。所以用悲觀鎖/樂觀鎖的思想標準來看鎖,發現各種各樣的鎖定義其實都在這二者的思想範圍內,沒有能跑出該範圍的。

    獨佔鎖/共享鎖(讀寫鎖)屬於鎖的佔用型別,有這兩種分類的原因就是為了提高系統的併發能力--讀不影響讀,這裡可以再引申一步,mysql為了進一步提高併發能力通過資料多版本使得讀跟寫也不衝突。其實這裡就是提高併發能力的一條線:先是不管讀請求還是寫請求統統排隊進行,後來將讀寫分開提高讀讀的效率,再後來通過資料多版本使得讀寫可以同時進行,後面還能再提高嗎?或許參考redis的單執行緒記憶體操作是一個方向,但對於複雜資料形式和大資料量卻不適合。

    公平鎖和非公平鎖主要針對的是獲取鎖的方式,公平就是一起排隊先排隊的先獲取到鎖,而非公平則表示一起競爭後來的也可能先獲取到鎖。公平鎖的應用場景很少,而且主要是通過lock鎖實現的,平時基本都是用非公平鎖,無他,非公平鎖併發量比公平的強了不止一點。但要額外說一下,lock鎖中的非公平模式並不是完全的非公平,如果兩次獲取不到鎖則進入阻塞佇列,進入阻塞佇列中後,它就只能按佇列中的順序挨個獲取鎖了,所以lock鎖中的非公平模式並不是徹頭徹尾的非公平,世間尚存一絲公道。。。

    重入鎖和非重入鎖屬於鎖的性質,這個很好理解,可以同一獲取鎖的來源能重複獲取的鎖就是可重入的,非可重入的場景很少,我們平時接觸的基本都是可重入。可重入的實現,基本原理都是在獲取到鎖之後,將物件記錄下來,下次再觸發獲取鎖的操作時先比對一下當前物件與已記錄物件是否是同一個,是的話則能獲取到鎖,鎖計數+1。詳情可見ReentrantLock的加鎖過程。

    由於synchronized鎖是JVM本身自帶的關鍵字,所以針對synchronized鎖做了很多優化,偏向鎖/輕量級鎖/自旋鎖/重量級鎖等概念都是來自於此(對synchronized鎖等講解可移步博主之前寫過的一/二/三系列【https://i.cnblogs.com/posts?cateId=1466867&page=1】)。其實從原理上來說,自旋鎖不是鎖,只是在獲取不到鎖時先自迴圈一定次數繼續嘗試獲取鎖,如果仍然獲取不到再阻塞,是針對很快能獲取到鎖的場景進行的優化處理。

下面再來幾張圖梳理下思緒:

 

 

 

 

 

相關文章