偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

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

開發(fā) 后端
java中AQS是AbstractQueuedSynchronizer類,AQS依賴FIFO隊列來提供一個框架,這個框架用于實現(xiàn)鎖以及鎖相關(guān)的同步器,比如信號量、事件等。

本文轉(zhuǎn)載自微信公眾號「程序員jinjunzhu」,作者jinjunzhu。轉(zhuǎn)載本文請聯(lián)系程序員jinjunzhu公眾號。

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

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

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

管程模型

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

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

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

獲取獨(dú)占鎖

獨(dú)占, 忽略interrupts

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

這里的tryAcquire是抽象方法,有AQS的子類來實現(xiàn),因為每個子類實現(xiàn)的鎖是不一樣的。

入隊

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

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

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

入隊后獲取鎖

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

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

1.waitStatus

  • CANCELLED(1):當(dāng)前節(jié)點(diǎn)取消獲取鎖。當(dāng)?shù)却瑫r或被中斷(響應(yīng)中斷),會觸發(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方法,這個結(jié)點(diǎn)將從等待隊列轉(zhuǎn)移到同步隊列隊尾,等待獲取同步鎖。
  • PROPAGATE(-3):共享模式,前置節(jié)點(diǎn)喚醒后面節(jié)點(diǎn)后,喚醒操作無條件傳播下去。
  • 0:中間狀態(tài),當(dāng)前節(jié)點(diǎn)后面的節(jié)點(diǎn)已經(jīng)喚醒,但是當(dāng)前節(jié)點(diǎn)線程還沒有執(zhí)行完成。

2.獲取鎖失敗后掛起

如果前置節(jié)點(diǎn)不是頭節(jié)點(diǎn),或者前置節(jié)點(diǎn)是頭節(jié)點(diǎn)但當(dāng)前節(jié)點(diǎn)獲取鎖失敗,這時當(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.對中斷狀態(tài)忽略

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

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

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

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

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

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

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

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

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

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

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

釋放獨(dú)占鎖

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

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

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

  • 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

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

  1. public final void acquireShared(int arg) { 
  2.     if (tryAcquireShared(arg) < 0) 
  3.         doAcquireShared(arg); 

tryAcquireShared

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

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

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

doAcquireShared

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

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

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

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

這里會造成不必要的喚醒,因為喚醒后獲取不到鎖只能繼續(xù)入隊等待

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

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

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

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

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

獲取鎖之前會檢查當(dāng)前線程是否中斷。

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

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

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

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

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

自旋獲取鎖的過程中每次獲取鎖失敗都要判斷是否超時

獲取鎖失敗park之前要判斷超時時間是否大于自旋的閾值時間(spinForTimeoutThreshold = 1ns)

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

釋放共享鎖

釋放共享鎖代碼如下:

  1. public final boolean releaseShared(int arg) { 
  2.     if (tryReleaseShared(arg)) { 
  3.         doReleaseShared(); 
  4.         return true
  5.     } 
  6.     return false

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

自旋的條件是隊列中至少有兩個節(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,為什么會這樣呢?需要解釋一下,head節(jié)點(diǎn)喚醒后繼節(jié)點(diǎn)之前waitStatus已經(jīng)被更新中間態(tài)0了,喚醒后繼節(jié)點(diǎn)動作還沒有執(zhí)行,又被其他線程更成了-3,也就是其他線程釋放鎖執(zhí)行了上面情況二。這時需要先把waitStatus再更成0(在方法unparkSuccessor),如下圖:

抽象方法

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

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

參考2[2]

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

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

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

總結(jié)

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

 

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

 

責(zé)任編輯:武曉燕 來源: 程序員jinjunzhu
相關(guān)推薦

2022-02-28 11:10:42

ZGCG1收集器

2022-07-11 11:06:11

RocketMQ函數(shù).消費(fèi)端

2022-07-04 11:06:02

RocketMQ事務(wù)消息實現(xiàn)

2021-12-06 07:15:47

Pulsar地域復(fù)制

2020-10-16 08:26:38

AQS通信協(xié)作

2020-11-27 06:28:55

Spring循環(huán)依賴

2021-08-15 18:59:13

垃圾收集器JDK

2022-04-11 11:55:34

架構(gòu)技術(shù)調(diào)優(yōu)

2022-12-26 08:36:24

JavaMESA模型

2022-06-13 11:05:35

RocketMQ消費(fèi)者線程

2023-04-11 08:35:22

RocketMQ云原生

2022-06-11 18:15:26

KubernetesDockerLinux

2021-04-25 10:45:59

Docker架構(gòu)Job

2024-07-03 08:28:44

HWKafkaLEO

2015-07-13 10:23:23

Java圖解

2021-10-22 09:28:15

開發(fā)技能代碼

2020-11-03 10:32:48

回調(diào)函數(shù)模塊

2020-10-16 06:30:45

分布式場景方案

2022-06-27 11:04:24

RocketMQ順序消息

2020-11-13 10:29:37

流程控制語句
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號