<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          扔掉源碼,15張圖帶你徹底理解java AQS

          共 5059字,需瀏覽 11分鐘

           ·

          2021-09-29 11:16

          java中AQS是AbstractQueuedSynchronizer,AQS依賴FIFO隊(duì)列來(lái)提供一個(gè)框架,這個(gè)框架用于實(shí)現(xiàn)鎖以及鎖相關(guān)的同步器,比如信號(hào)量、事件等。

          在AQS中,主要有兩部分功能,一部分是操作state變量,第二部分是實(shí)現(xiàn)排隊(duì)和阻塞機(jī)制。

          注意,AQS并沒(méi)有實(shí)現(xiàn)任何同步接口,它只是提供了類似acquireInterruptible的方法,調(diào)用這些方法可以實(shí)現(xiàn)鎖和同步器。

          管程模型

          java使用MESA管程模型來(lái)管理類的成員變量和方法,讓這個(gè)類的成員變量和方法的操作是線程安全的。下圖是MESA管程模型,里面除了定義共享變量外,還定義了條件變量和條件變量等待隊(duì)列:

          java中的MESA模型有一點(diǎn)改進(jìn),就是管程內(nèi)部只有一個(gè)條件變量和一個(gè)等待隊(duì)列。下圖是AQS的管程模型:

          AQS的管程模型依賴AQS中的FIFO隊(duì)列實(shí)現(xiàn)入口等待隊(duì)列,而ConditionObject則實(shí)現(xiàn)了條件隊(duì)列,這個(gè)隊(duì)列可以創(chuàng)建多個(gè)。本文主要講解入口等待隊(duì)列獲取鎖的幾種方式。參考1[1]

          獲取獨(dú)占鎖

          獨(dú)占, 忽略interrupts

          public final void acquire(int arg) {
              if (!tryAcquire(arg) &&
                  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                  selfInterrupt();
          }

          這里的tryAcquire是抽象方法,有AQS的子類來(lái)實(shí)現(xiàn),因?yàn)槊總€(gè)子類實(shí)現(xiàn)的鎖是不一樣的。

          入隊(duì)

          上面的代碼可以看到,獲取鎖失敗后,會(huì)先執(zhí)行addWaiter方法加入隊(duì)列,然后執(zhí)行acquireQueued方法自旋地獲取鎖直到成功。

          addWaiter代碼邏輯如下圖,簡(jiǎn)單說(shuō)就是把node入隊(duì),入隊(duì)后返回node參數(shù)給acquireQueued方法:

          這里有一個(gè)點(diǎn)需要注意,如果隊(duì)列為空,則新建一個(gè)Node作為隊(duì)頭。

          入隊(duì)后獲取鎖

          acquireQueued自旋獲取鎖邏輯如下圖:

          這里有幾個(gè)細(xì)節(jié):

          1.waitStatus

          • CANCELLED(1):當(dāng)前節(jié)點(diǎn)取消獲取鎖。當(dāng)?shù)却瑫r(shí)或被中斷(響應(yīng)中斷),會(huì)觸發(fā)變更為此狀態(tài),進(jìn)入該狀態(tài)后節(jié)點(diǎn)狀態(tài)不再變化。
          • SIGNAL(-1):后面節(jié)點(diǎn)等待當(dāng)前節(jié)點(diǎn)喚醒。
          • CONDITION(-2):Condition中使用,當(dāng)前線程阻塞在Condition,如果其他線程調(diào)用了Condition的signal方法,這個(gè)結(jié)點(diǎn)將從等待隊(duì)列轉(zhuǎn)移到同步隊(duì)列隊(duì)尾,等待獲取同步鎖。
          • PROPAGATE(-3):共享模式,前置節(jié)點(diǎn)喚醒后面節(jié)點(diǎn)后,喚醒操作無(wú)條件傳播下去。
          • 0:中間狀態(tài),當(dāng)前節(jié)點(diǎn)后面的節(jié)點(diǎn)已經(jīng)喚醒,但是當(dāng)前節(jié)點(diǎn)線程還沒(méi)有執(zhí)行完成。

          2.獲取鎖失敗后掛起

          如果前置節(jié)點(diǎn)不是頭節(jié)點(diǎn),或者前置節(jié)點(diǎn)是頭節(jié)點(diǎn)但當(dāng)前節(jié)點(diǎn)獲取鎖失敗,這時(shí)當(dāng)前節(jié)點(diǎn)需要掛起,分三種情況,

          前置節(jié)點(diǎn)waitStatus=-1,如下圖:

          前置節(jié)點(diǎn)waitStatus > 0,如下圖:

          前置節(jié)點(diǎn)waitStatus < 0 但不等于 -1,如下圖:

          3.取消獲取鎖

          如果獲取鎖拋出異常,則取消獲取鎖,如果當(dāng)前節(jié)點(diǎn)是tail節(jié)點(diǎn),分兩種情況如下圖:

          如果當(dāng)前節(jié)點(diǎn)不是tail節(jié)點(diǎn),也分兩種情況,如下圖:

          4.對(duì)中斷狀態(tài)忽略

          5.如果前置節(jié)點(diǎn)的狀態(tài)是 0 或 PROPAGATE,會(huì)被當(dāng)前節(jié)點(diǎn)自旋過(guò)程中更新成-1,以便之后通知當(dāng)前節(jié)點(diǎn)。

          獨(dú)占 + 響應(yīng)中斷

          對(duì)應(yīng)方法acquireInterruptibly(int arg)。

          跟忽略中斷(acquire方法)不同的是要響應(yīng)中斷,下面兩個(gè)地方響應(yīng)中斷:

          • 獲取鎖之前會(huì)檢查當(dāng)前線程是否中斷。
          • 獲取鎖失敗入隊(duì),在隊(duì)列中自旋獲取鎖的過(guò)程中也會(huì)檢查當(dāng)前線程是否中斷。

          如果檢查到當(dāng)前線程已經(jīng)中斷,則拋出InterruptedException,當(dāng)前線程退出。

          獨(dú)占 + 響應(yīng)中斷 + 考慮超時(shí)

          對(duì)應(yīng)方法tryAcquireNanos(int arg, long nanosTimeout)。

          這個(gè)方法具備了獨(dú)占 + 響應(yīng)中斷 + 超時(shí)的功能,下面2個(gè)地方要判斷是否超時(shí):

          • 自旋獲取鎖的過(guò)程中每次獲取鎖失敗都要判斷是否超時(shí)
          • 獲取鎖失敗park之前要判斷超時(shí)時(shí)間是否大于自旋的閾值時(shí)間**(spinForTimeoutThreshold = 1ns)**

          另外,park線程的操作使用parkNanos傳入阻塞時(shí)間。

          釋放獨(dú)占鎖

          獨(dú)占鎖釋放分兩步:釋放鎖,喚醒后繼節(jié)點(diǎn)。

          釋放鎖的方法 tryRelease 是抽象的,由子類去實(shí)現(xiàn)。

          我們看一下喚醒后繼節(jié)點(diǎn)的邏輯,首先需要滿足兩個(gè)條件:

          • head節(jié)點(diǎn)不等于 null
          • head節(jié)點(diǎn)waitStatus不等于0

          這里有兩種情況(在方法unparkSuccessor):

          • 情況一,后繼節(jié)點(diǎn)waitStatus <= 0,直接喚醒后繼節(jié)點(diǎn),如下圖:
          • 情況二:后繼節(jié)點(diǎn)為空或者waitStatus > 0,從后往前查找最接近當(dāng)前節(jié)點(diǎn)的節(jié)點(diǎn)進(jìn)行喚醒,如下圖:

          獲取共享鎖

          之前我們講了獨(dú)占鎖,這一小節(jié)我們談共享鎖,有什么不同呢?

          共享,忽略interrupts

          對(duì)應(yīng)方法acquireShared,代碼如下:

          public final void acquireShared(int arg) {
              if (tryAcquireShared(arg) < 0)
                  doAcquireShared(arg);
          }

          tryAcquireShared

          這里獲取鎖使用的方法是tryAcquireShared,獲取的是共享鎖。獲取共享鎖跟獲取獨(dú)占鎖不同的是,會(huì)返回一個(gè)整數(shù)值,說(shuō)明如下:

          • 返回負(fù)數(shù):獲取鎖失敗。
          • 返回0:獲取鎖成功但是之后再由線程來(lái)獲取共享鎖時(shí)就會(huì)失敗。
          • 返回正數(shù):獲取鎖成功而且之后再有線程來(lái)獲取共享鎖時(shí)也可能會(huì)成功。所以需要把喚醒操作傳播下去。

          tryAcquireShared獲取鎖失敗后(返回負(fù)數(shù)),就需要入隊(duì)后自旋獲取,也就是執(zhí)行方法doAcquireShared。

          doAcquireShared

          怎么判斷隊(duì)列中等待節(jié)點(diǎn)是在等待共享鎖呢?nextWaiter == SHARED,這個(gè)參數(shù)值是入隊(duì)新建節(jié)點(diǎn)的時(shí)候構(gòu)造函數(shù)傳入的。

          自旋過(guò)程中,如果獲取鎖成功(返回正數(shù)),首先把自己設(shè)置成新的head節(jié)點(diǎn),然后把通知傳播下去。如下圖:net/anlian523/article/details/106319294/

          之后會(huì)喚醒后面節(jié)點(diǎn)并保證喚醒操作可以傳播下去。但是需要滿足四個(gè)條件中的一個(gè):

          • tryAcquireShared返回值大于0,有多余的鎖,可以繼續(xù)喚醒后繼節(jié)點(diǎn)
          • 舊的head節(jié)點(diǎn)waitStatus < 0,應(yīng)該是其他線程釋放共享鎖過(guò)程中把它的狀態(tài)更新成了-3
          • 新的hade節(jié)點(diǎn)waitStatus < 0,只要不是tail節(jié)點(diǎn),就可能是-1

          這里會(huì)造成不必要的喚醒,因?yàn)閱拘押螳@取不到鎖只能繼續(xù)入隊(duì)等待

          • 當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)是空或者非空但正在等待共享鎖

          喚醒后面節(jié)點(diǎn)的操作,其實(shí)就是釋放共享鎖,對(duì)應(yīng)方法是doReleaseShared,見(jiàn)釋放共享鎖一節(jié)。

          共享 + 響應(yīng)中斷

          對(duì)應(yīng)方法acquireSharedInterruptibly(int arg)。

          跟共享忽略中斷(acquireShared方法)不同的是要響應(yīng)中斷,下面兩個(gè)地方響應(yīng)中斷:

          • 獲取鎖之前會(huì)檢查當(dāng)前線程是否中斷。
          • 獲取鎖失敗入隊(duì),在隊(duì)列中自旋獲取鎖的過(guò)程中也會(huì)檢查當(dāng)前線程是否中斷。

          如果檢查到當(dāng)前線程已經(jīng)中斷,則拋出InterruptedException,當(dāng)前線程退出。

          共享 + 響應(yīng)中斷 + 考慮超時(shí)

          對(duì)應(yīng)方法tryAcquireSharedNanos(int arg, long nanosTimeout)。

          這個(gè)方法具備了共享 + 響應(yīng)中斷 + 超時(shí)的功能,下面2個(gè)地方要判斷是否超時(shí):

          • 自旋獲取鎖的過(guò)程中每次獲取鎖失敗都要判斷是否超時(shí)
          • 獲取鎖失敗park之前要判斷超時(shí)時(shí)間是否大于自旋的閾值時(shí)間(spinForTimeoutThreshold = 1ns)

          另外,park線程的操作使用parkNanos傳入阻塞時(shí)間。

          釋放共享鎖

          釋放共享鎖代碼如下:

          public final boolean releaseShared(int arg) {
              if (tryReleaseShared(arg)) {
                  doReleaseShared();
                  return true;
              }
              return false;
          }

          首先嘗試釋放共享鎖,tryReleaseShared代碼由子類來(lái)實(shí)現(xiàn)。釋放成功后執(zhí)行AQS中的doReleaseShared方法,是一個(gè)自旋操作。

          自旋的條件是隊(duì)列中至少有兩個(gè)節(jié)點(diǎn),這里分三種情況。

          情況一:當(dāng)前節(jié)點(diǎn)waitStatus是-1,如下圖:

          情況二:當(dāng)前節(jié)點(diǎn)waitStatus是0(被其他線程更xin新成了中間狀態(tài)),如下圖:

          情況三:當(dāng)前節(jié)點(diǎn)waitStatus是-3,為什么會(huì)這樣呢?需要解釋一下,head節(jié)點(diǎn)喚醒后繼節(jié)點(diǎn)之前waitStatus已經(jīng)被更新中間態(tài)0了,喚醒后繼節(jié)點(diǎn)動(dòng)作還沒(méi)有執(zhí)行,又被其他線程更成了-3,也就是其他線程釋放鎖執(zhí)行了上面情況二。這時(shí)需要先把waitStatus再更成0(在方法unparkSuccessor),如下圖:

          抽象方法

          上面的講解可以看出,如果要基于AQS來(lái)實(shí)現(xiàn)并發(fā)鎖,可以根據(jù)需求重寫下面四個(gè)方法來(lái)實(shí)現(xiàn),這四個(gè)方法在AQS中沒(méi)有具體實(shí)現(xiàn):

          • tryAcquire(int arg):獲取獨(dú)占鎖
          • tryRelease(int arg):釋放獨(dú)占鎖
          • tryAcquireShared(int arg):獲取共享鎖
          • tryReleaseShared(int arg):釋放共享鎖 
            參考2[2]

          AQS的子類需要重寫上面的方法來(lái)修改state值,并且定義獲取鎖或者釋放鎖時(shí)state值的變化。子類也可以定義自己的state變量,但是只有更新AQS中的state變量才會(huì)對(duì)同步起作用。

          還有一個(gè)判斷當(dāng)前線程是否持有獨(dú)占鎖的方法 isHeldExclusively,也可以供子類重寫后使用。

          獲取/釋放鎖的具體實(shí)現(xiàn)放到下篇文章講解。

          總結(jié)

          AQS使用FIFO隊(duì)列實(shí)現(xiàn)了一個(gè)鎖相關(guān)的并發(fā)器模板,可以基于這個(gè)模板來(lái)實(shí)現(xiàn)各種鎖,包括獨(dú)占鎖、共享鎖、信號(hào)量等。

          AQS中,有一個(gè)核心狀態(tài)是waitStatus,這個(gè)代表節(jié)點(diǎn)的狀態(tài),決定了當(dāng)前節(jié)點(diǎn)的后續(xù)操作,比如是否等待喚醒,是否要喚醒后繼節(jié)點(diǎn)。

               ··············  END  ··············

          歡迎大家加我微信,圍觀朋友圈,做點(diǎn)贊之交,一起進(jìn)步。想要進(jìn)技術(shù)交流群的朋友,加我微信回復(fù)進(jìn)群

          參考資料

          [1]

          參考1: https://blog.csdn.net/it_lihongmin/article/details/109609023

          [2]

          參考2: https://mp.weixin.qq.com/s/u_0fAgTUtC0YUz-SIxlL7Q


          瀏覽 41
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九九九九在线视频 | 亚洲午夜精品久久久久久APP | 黄片国产乱轮 | 做爱污污短视屏在线观看 | 中国一区二区操B视频 |