日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

reentrantlock非公平锁不会随机挂起线程?_程序员必须要知道的ReentrantLock 及 AQS 实现原理...

發布時間:2023/12/10 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 reentrantlock非公平锁不会随机挂起线程?_程序员必须要知道的ReentrantLock 及 AQS 实现原理... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

專注于Java領域優質技術,歡迎關注

作者:Float_Luuu

提到 JAVA 加鎖,我們通常會想到 synchronized 關鍵字或者是 Java Concurrent Util(后面簡稱JCU)包下面的 Lock,今天就來扒一扒 Lock 是如何實現的,比如我們可以先提出一些問題:當我們通實例化一個 ReentrantLock 并且調用它的 lock 或 unlock 的時候,這其中發生了什么?如果多個線程同時對同一個鎖實例進行 lock 或 unlcok 操作,這其中又發生了什么?

什么是可重入鎖?

ReentrantLock 是可重入鎖,什么是可重入鎖呢?可重入鎖就是當前持有該鎖的線程能夠多次獲取該鎖,無需等待。可重入鎖是如何實現的呢?這要從 ReentrantLock 的一個內部類 Sync 的父類說起,Sync 的父類是 AbstractQueuedSynchronizer(后面簡稱AQS)。

什么是AQS?

AQS 是 JDK1.5 提供的一個基于 FIFO 等待隊列實現的一個用于實現同步器的基礎框架,這個基礎框架的重要性可以這么說,JCU 包里面幾乎所有的有關鎖、多線程并發以及線程同步器等重要組件的實現都是基于 AQS 這個框架。AQS 的核心思想是基于 volatile int state 這樣的一個屬性同時配合 Unsafe 工具對其原子性的操作來實現對當前鎖的狀態進行修改。當 state 的值為 0 的時候,標識改 Lock 不被任何線程所占有。

ReentrantLock 鎖的架構

ReentrantLock 的架構相對簡單,主要包括一個 Sync 的內部抽象類以及 Sync 抽象類的兩個實現類。上面已經說過了 Sync 繼承自 AQS,他們的結構示意圖如下:

上圖除了 AQS 之外,我把 AQS 的父類 AbstractOwnableSynchronizer(后面簡稱AOS)也畫了進來,可以稍微提一下,AOS 主要提供一個 exclusiveOwnerThread 屬性,用于關聯當前持有該所的線程。另外、Sync 的兩個實現類分別是 NonfairSync 和 FairSync,由名字大概可以猜到,一個是用于實現公平鎖、一個是用于實現非公平鎖。那么 Sync 為什么要被設計成內部類呢?我們可以看看 AQS 主要提供了哪些 protect 的方法用于修改 state 的狀態,我們發現 Sync 被設計成為安全的外部不可訪問的內部類。ReentrantLock 中所有涉及對 AQS 的訪問都要經過 Sync,其實,Sync 被設計成為內部類主要是為了安全性考慮,這也是作者在 AQS 的 comments 上強調的一點。

AQS 的等待隊列

作為 AQS 的核心實現的一部分,舉個例子來描述一下這個隊列長什么樣子,我們假設目前有三個線程 Thread1、Thread2、Thread3 同時去競爭鎖,如果結果是 Thread1 獲取了鎖,Thread2 和 Thread3 進入了等待隊列,那么他們的樣子如下:

AQS 的等待隊列基于一個雙向鏈表實現的,HEAD 節點不關聯線程,后面兩個節點分別關聯 Thread2 和 Thread3,他們將會按照先后順序被串聯在這個隊列上。這個時候如果后面再有線程進來的話將會被當做隊列的 TAIL。

1)入隊列

我們來看看,當這三個線程同時去競爭鎖的時候發生了什么?代碼:

解讀:三個線程同時進來,他們會首先會通過 CAS 去修改 state 的狀態,如果修改成功,那么競爭成功,因此這個時候三個線程只有一個 CAS 成功,其他兩個線程失敗,也就是 tryAcquire 返回 false。接下來,addWaiter 會把將當前線程關聯的 EXCLUSIVE 類型的節點入隊列:

解讀:如果隊尾節點不為 null,則說明隊列中已經有線程在等待了,那么直接入隊尾。對于我們舉的例子,這邊的邏輯應該是走 enq,也就是開始隊尾是 null,其實這個時候整個隊列都是 null 的。代碼:

解讀:如果 Thread2 和 Thread3 同時進入了 enq,同時 t==null,則進行 CAS 操作對隊列進行初始化,這個時候只有一個線程能夠成功,然后他們繼續進入循環,第二次都進入了 else 代碼塊,這個時候又要進行 CAS 操作,將自己放在隊尾,因此這個時候又是只有一個線程成功,我們假設是 Thread2 成功,哈哈,Thread2 開心的返回了,Thread3 失落的再進行下一次的循環,最終入隊列成功,返回自己。

2)并發問題

基于上面兩段代碼,他們是如何實現不進行加鎖,當有多個線程,或者說很多很多的線程同時執行的時候,怎么能保證最終他們都能夠乖乖的入隊列而不會出現并發問題的呢?這也是這部分代碼的經典之處,多線程競爭,熱點、單點在隊列尾部,多個線程都通過【CAS+死循環】這個free-lock黃金搭檔來對隊列進行修改,每次能夠保證只有一個成功,如果失敗下次重試,如果是N個線程,那么每個線程最多 loop N 次,最終都能夠成功。

3)掛起等待線程

上面只是 addWaiter 的實現部分,那么節點入隊列之后會繼續發生什么呢?那就要看看 acquireQueued 是怎么實現的了,為保證文章整潔,代碼我就不貼了,同志們自行查閱,我們還是以上面的例子來看看,Thread2 和 Thread3 已經被放入隊列了,進入 acquireQueued 之后:

  • 對于 Thread2 來說,它的 prev 指向 HEAD,因此會首先再嘗試獲取鎖一次,如果失敗,則會將 HEAD 的 waitStatus 值為 SIGNAL,下次循環的時候再去嘗試獲取鎖,如果還是失敗,且這個時候 prev 節點的 waitStatus 已經是 SIGNAL,則這個時候線程會被通過 LockSupport 掛起。
  • 對于 Thread3 來說,它的 prev 指向 Thread2,因此直接看看 Thread2 對應的節點的 waitStatus 是否為 SIGNAL,如果不是則將它設置為 SIGNAL,再給自己一次去看看自己有沒有資格獲取鎖,如果 Thread2 還是擋在前面,且它的 waitStatus 是 SIGNAL,則將自己掛起。
  • 如果 Thread1 死死的握住鎖不放,那么 Thread2 和 Thread3 現在的狀態就是掛起狀態啦,而且 HEAD,以及 Thread 的 waitStatus 都是 SIGNAL,盡管他們在整個過程中曾經數次去嘗試獲取鎖,但是都失敗了,失敗了不能死循環呀,所以就被掛起了。當前狀態如下:

    鎖釋放-等待線程喚起

    我們來看看當 Thread1 這個時候終于做完了事情,調用了 unlock 準備釋放鎖,這個時候發生了什么。代碼:

    解讀:首先,Thread1 會修改AQS的state狀態,加入之前是 1,則變為 0,注意這個時候對于非公平鎖來說是個很好的插入機會,舉個例子,如果鎖是公平鎖,這個時候來了 Thread4,那么這個鎖將會被 Thread4 搶去。。。

    我們繼續走常規路線來分析,當 Thread1 修改完狀態了,判斷隊列是否為 null,以及隊頭的 waitStatus 是否為 0,如果 waitStatus 為 0,說明隊列無等待線程,按照我們的例子來說,隊頭的 waitStatus 為 SIGNAL=-1,因此這個時候要通知隊列的等待線程,可以來拿鎖啦,這也是 unparkSuccessor 做的事情,unparkSuccessor 主要做三件事情:

  • 將隊頭的 waitStatus 設置為 0。
  • 通過從隊列尾部向隊列頭部移動,找到最后一個 waitStatus<=0 的那個節點,也就是離隊頭最近的沒有被cancelled的那個節點,隊頭這個時候指向這個節點。
  • 將這個節點喚醒,其實這個時候 Thread1 已經出隊列了。
  • 還記得線程在哪里掛起的么,上面說過了,在 acquireQueued 里面,我沒有貼代碼,自己去看哦。這里我們也大概能理解 AQS 的這個隊列為什么叫 FIFO 隊列了,因此每次喚醒僅僅喚醒隊頭等待線程,讓隊頭等待線程先出。

    羊群效應

    這里說一下羊群效應,當有多個線程去競爭同一個鎖的時候,假設鎖被某個線程占用,那么如果有成千上萬個線程在等待鎖,有一種做法是同時喚醒這成千上萬個線程去去競爭鎖,這個時候就發生了羊群效應,海量的競爭必然造成資源的劇增和浪費,因此終究只能有一個線程競爭成功,其他線程還是要老老實實的回去等待。AQS 的 FIFO 的等待隊列給解決在鎖競爭方面的羊群效應問題提供了一個思路:保持一個 FIFO 隊列,隊列每個節點只關心其前一個節點的狀態,線程喚醒也只喚醒隊頭等待線程。其實這個思路已經被應用到了分布式鎖的實踐中,見:Zookeeper 分布式鎖的改進實現方案。

    總結

    這篇文章粗略的介紹一下 ReentrantLock 以及鎖實現基礎框架 AQS 的實現原理,大致上通過舉了個三個線程競爭鎖的例子,從 lock、unlock 過程發生了什么這個問題,深入了解 AQS 基于狀態的標識以及 FIFO 等待隊列方面的工作原理,最后擴展介紹了一下羊群效應問題,博主才疏學淺,還請多多指教。

    總結

    以上是生活随笔為你收集整理的reentrantlock非公平锁不会随机挂起线程?_程序员必须要知道的ReentrantLock 及 AQS 实现原理...的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。