深入淺出Java鎖--Lock實現原理(底層實現)

深入淺出Java鎖--Lock實現原理(底層實現)

轉載Linias 最後發佈於2018-12-21 16:25:53 閱讀數 2296  收藏

展開

當多個線程需要訪問某個公共資源的時候,我們知道需要通過加鎖來保證資源的訪問不會出問題。

java提供了兩種方式來加鎖,一種是關鍵字:synchronized,一種是concurrent包下的lock鎖。

synchronized是java底層支持的,而concurrent包則是jdk實現

 

關於synchronized的原理可以閱讀再有人問你synchronized是什麼,就把這篇文章發給他。

在這裏,我會用儘可能少的代碼,儘可能輕鬆的文字,儘可能多的圖來看看lock的原理。

我們以ReentrantLock爲例做分析,其他原理類似。

 

我把這個過程比喻成一個做菜的過程,有什麼菜,做法如何?

我先列出lock實現過程中的幾個關鍵詞:計數值、雙向鏈表、CAS+自旋

我們以ReentrantLock爲例做分析,其他原理類似。

實現原理

ReentrantLock() 幹了啥


 
  1.   public ReentrantLock() {

  2.  
  3.         sync = new NonfairSync();

  4.  
  5.     }

在lock的構造函數中,定義了一個NonFairSync

static final class NonfairSync extends Sync

NonfairSync 又是繼承於Sync

abstract static class Sync extends AbstractQueuedSynchronizer

一步一步往上找,找到了

這個鬼AbstractQueuedSynchronizer(簡稱AQS),最後這個鬼,又是繼承於AbstractOwnableSynchronizer(AOS),AOS主要是保存獲取當前鎖的線程對象,代碼不多不再展開。最後我們可以看到幾個主要類的繼承關係:

                           

   FairSync 與 NonfairSync的區別在於,是不是保證獲取鎖的公平性,因爲默認是NonfairSync,我們以這個爲例瞭解其背後的原理。

其他幾個類代碼不多,最後的主要代碼都是在AQS中,我們先看看這個類的主體結構。

看看AbstractQueuedSynchronizer是個什麼

再看看Node是什麼?

看到這裏的同學,是不是有種熱淚盈眶的感覺,這尼瑪,不就是雙向鏈表麼?我還記得第一次寫這個數據結構的時候,發現居然還有這麼神奇的一個東西。

最後我們可以發現鎖的存儲結構就兩個東西:"雙向鏈表" + "int類型狀態"。

需要注意的是,他們的變量都被"transientvolatile修飾。

一個int值,一個雙向鏈表是如何烹飪處理鎖這道菜的呢,Doug Lea大神就是大神,

我們接下來看看,如何獲取鎖?

lock.lock()怎麼獲取鎖?


 
  1. public void lock() {

  2.  
  3.     sync.lock();

  4.  
  5. }

可以看到調用的是,NonfairSync.lock()

看到這裏,我們基本有了一個大概的瞭解,還記得之前AQS中的int類型的state值

這裏就是通過CAS(樂觀鎖)去修改state的值。lock的基本操作還是通過樂觀鎖來實現的。

獲取鎖通過CAS,那麼沒有獲取到鎖,等待獲取鎖是如何實現的?我們可以看一下else分支的邏輯,acquire方法:


 
  1. public final void acquire(int arg) {

  2.  
  3.     if (!tryAcquire(arg) &&

  4.  
  5.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

  6.  
  7.         selfInterrupt();

  8.  
  9. }

這裏幹了三件事情:

  • tryAcquire:會嘗試再次通過CAS獲取一次鎖。

  • addWaiter:將當前線程加入上面鎖的雙向鏈表(等待隊列)中

  • acquireQueued:通過自旋,判斷當前隊列節點是否可以獲取鎖。

 

addWaiter() 添加當前線程到等待鏈表中

可以看到,通過CAS確保能夠在線程安全的情況下,將當前線程加入到鏈表的尾部。

enq是個自旋+上述邏輯,有興趣的可以翻翻源碼。

 

acquireQueued()    自旋+CAS嘗試獲取鎖

可以看到,噹噹前線程到頭部的時候,嘗試CAS更新鎖狀態,如果更新成功表示該等待線程獲取成功。從頭部移除。

 

每一個線程都在 自旋+CAS

最後簡要概括一下,獲取鎖的一個流程


lock.unlock() 釋放鎖


 
  1. public void unlock() {

  2.  
  3.     sync.release(1);

  4.  
  5. }

可以看到調用的是,NonfairSync.release()

最後又調用了NonfairSync.tryRelease()

基本可以確認,釋放鎖就是對AQS中的狀態值State進行修改。同時更新下一個鏈表中的線程等待節點

總結

lock的存儲結構:一個int類型狀態值(用於鎖的狀態變更),一個雙向鏈表(用於存儲等待中的線程)

lock獲取鎖的過程:本質上是通過CAS來獲取狀態值修改,如果當場沒獲取到,會將該線程放在線程等待鏈表中。

lock釋放鎖的過程:修改狀態值,調整等待鏈表。

可以看到在整個實現過程中,lock大量使用CAS+自旋。因此根據CAS特性,lock建議使用在低鎖衝突的情況下。目前java1.6以後,官方對synchronized做了大量的鎖優化(偏向鎖、自旋、輕量級鎖)。因此在非必要的情況下,建議使用synchronized做同步操作。

最後,希望我的分析,能對你理解鎖的實現有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章