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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

J.U.C--locks--AQS分析

發(fā)布時(shí)間:2025/4/16 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 J.U.C--locks--AQS分析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

看一下AbstractQueuedSynchronizer(下面簡(jiǎn)稱AQS)的子類就行知道,J.U.C中宣傳的封裝良好的同步工具類Semaphore、CountDownLatch、ReentrantLock、ReentrantReadWriteLock、FutureTask等盡管各自都有不同特征,可是其內(nèi)部的實(shí)現(xiàn)都與AQS分不開。

所以分析AQS的實(shí)現(xiàn)原理對(duì)其余顯示鎖或則同步工具類的理解非常重要。

1.主要屬性和內(nèi)部類

這一篇blog主要分析AQS的實(shí)現(xiàn)中的重要屬性和內(nèi)部類。尤其是對(duì)于ReentrantLock和ReentrantReadWriteLock。其lock()方法和unlock()方法的實(shí)現(xiàn)終于都是由AQS同步器實(shí)現(xiàn)的。由此可見(jiàn)分析AQS類的重要性可見(jiàn)一斑。

在AQS中,我們先看屬性遠(yuǎn)比看方法來(lái)的更加easy理解這個(gè)類的作用。首先看AQS類的主要屬性:

//等待隊(duì)列的頭指針 private transient volatile Node head; //等待隊(duì)列的尾指針 private transient volatile Node tail; //同步器的狀態(tài)位,注意這里state是聲明了volatile。保證了可視性 private volatile int state;

凝視事實(shí)上已經(jīng)告訴我們了。Node類型的 head 和 tail 是一個(gè)FIFO的wait queue。一個(gè)int類型的狀態(tài)位state。到這里也能猜到AQS對(duì)外呈現(xiàn)(或者說(shuō)聲明)的主要行為就是由一個(gè)狀態(tài)位和一個(gè)有序隊(duì)列來(lái)配合完畢。

state屬性

對(duì)于state狀態(tài)的管理,在AQS中僅僅通過(guò)三個(gè)方法來(lái)實(shí)現(xiàn):

java.util.concurrent.locks.AbstractQueuedSynchronizer.getState();java.util.concurrent.locks.AbstractQueuedSynchronizer.setState(int);java.util.concurrent.locks.AbstractQueuedSynchronizer.compareAndSetState(int, int);

前面兩個(gè)函數(shù)事實(shí)上就是get和set方法。

第三個(gè)函數(shù)事實(shí)上是通過(guò)Unsafe類實(shí)現(xiàn)CAS設(shè)置狀態(tài)值,CAS+volatile 保證了state變量的線程安全。

Node結(jié)點(diǎn)

前面還提到了同步器的實(shí)現(xiàn)還依賴于一個(gè)FIFO的隊(duì)列。隊(duì)列中的元素Node就是保存著線程引用和線程狀態(tài)的容器,每一個(gè)線程對(duì)同步器的訪問(wèn)。都可以看做是隊(duì)列中的一個(gè)節(jié)點(diǎn)。

Node類的源代碼不多,我直接所有粘貼出來(lái):

static final class Node {static final Node SHARED = new Node();static final Node EXCLUSIVE = null;static final int CANCELLED = 1;static final int SIGNAL = -1;static final int CONDITION = -2;static final int PROPAGATE = -3;volatile int waitStatus;volatile Node prev;volatile Node next;volatile Thread thread;Node nextWaiter;final boolean isShared() {return nextWaiter == SHARED;}final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}/** 構(gòu)造器 */Node() { // Used to establish initial head or SHARED marker}/** 構(gòu)造器 */Node(Thread thread, Node mode) { // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}/** 構(gòu)造器 */Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;} }

Node類主要有5個(gè)屬性:

volatile int waitStatus;// volatile Node prev;// volatile Node next;// volatile Thread thread;// Node nextWaiter;//

以上五個(gè)成員變量主要負(fù)責(zé)保存該節(jié)點(diǎn)的線程引用,同步等待隊(duì)列(下面簡(jiǎn)稱sync隊(duì)列)的前驅(qū)和后繼節(jié)點(diǎn)。同一時(shí)候也包括了同步狀態(tài)。
對(duì)這5個(gè)變量的解釋例如以下:

屬性名稱描寫敘述
int waitStatus表示節(jié)點(diǎn)的狀態(tài)。當(dāng)中包括的狀態(tài)有:
1.CANCELLED,值為1,表示當(dāng)前的線程被取消。
2.SIGNAL,值為-1。表示當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)包括的線程須要執(zhí)行,也就是unpark;
3.CONDITION,值為-2。表示當(dāng)前節(jié)點(diǎn)在等待condition,也就是在condition隊(duì)列中;
4.PROPAGATE,值為-3。表示當(dāng)前場(chǎng)景下興許的acquireShared可以得以執(zhí)行;
5.值為0,表示當(dāng)前節(jié)點(diǎn)在sync隊(duì)列中。等待著獲取鎖。
Node prev前驅(qū)節(jié)點(diǎn)。比方當(dāng)前節(jié)點(diǎn)被取消,那就須要前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)來(lái)完畢連接。
Node next后繼節(jié)點(diǎn)。

Node nextWaiter存儲(chǔ)condition隊(duì)列中的后繼節(jié)點(diǎn)。
Thread thread入隊(duì)列時(shí)的當(dāng)前線程。



節(jié)點(diǎn)成為sync隊(duì)列和condition隊(duì)列構(gòu)建的基礎(chǔ),在同步器中就包括了sync隊(duì)列。

同步器擁有三個(gè)成員變量:sync隊(duì)列的頭結(jié)點(diǎn)head、sync隊(duì)列的尾節(jié)點(diǎn)tail和狀態(tài)state。對(duì)于鎖的獲取。請(qǐng)求形成節(jié)點(diǎn),將其掛載在尾部。而鎖資源的轉(zhuǎn)移(釋放再獲取)是從頭部開始向后進(jìn)行。對(duì)于同步器維護(hù)的狀態(tài)state,多個(gè)線程對(duì)其的獲取將會(huì)產(chǎn)生一個(gè)鏈?zhǔn)降慕Y(jié)構(gòu)。

2.重要函數(shù)的源代碼解析

獲取鎖相關(guān)函數(shù)

acquire(int arg);//以獨(dú)占模式獲取對(duì)象,忽略中斷。 acquireInterruptibly(int arg);//以獨(dú)占模式獲取對(duì)象。假設(shè)被中斷則中止。 acquireShared(int arg);//以共享模式獲取對(duì)象,忽略中斷。 acquireSharedInterruptibly(int arg);//以共享模式獲取對(duì)象,假設(shè)被中斷則中止。tryAcquire(int arg);//試圖在獨(dú)占模式下獲取對(duì)象狀態(tài)。 tryAcquireNanos(int arg, long nanosTimeout);//試圖以獨(dú)占模式獲取對(duì)象,假設(shè)被中斷則中止。假設(shè)到了給定超時(shí)時(shí)間,則會(huì)失敗。 tryAcquireShared(int arg);//試圖在共享模式下獲取對(duì)象狀態(tài)。 tryAcquireSharedNanos(int arg, long nanosTimeout);//試圖以共享模式獲取對(duì)象,假設(shè)被中斷則中止。假設(shè)到了給定超時(shí)時(shí)間。則會(huì)失敗。

釋放鎖相關(guān)函數(shù)

release(int arg);//以獨(dú)占模式釋放對(duì)象。 releaseShared(int arg);//以共享模式釋放對(duì)象tryRelease(int arg);//試圖設(shè)置狀態(tài)來(lái)反映獨(dú)占模式下的一個(gè)釋放。 tryReleaseShared(int arg);//試圖設(shè)置狀態(tài)來(lái)反映共享模式下的一個(gè)釋放。

1)acquire(int arg)函數(shù)

首先看看Javadoc的定義:

以獨(dú)占模式獲取對(duì)象,忽略中斷。

通過(guò)至少調(diào)用一次 tryAcquire(int) 來(lái)實(shí)現(xiàn)此方法,并在成功時(shí)返回。否則在成功之前,一直調(diào)用 tryAcquire(int) 將線程加入隊(duì)列,線程可能反復(fù)被堵塞或不被堵塞。可以使用此方法來(lái)實(shí)現(xiàn) Lock.lock() 方法。

可知該函數(shù)是以獨(dú)占模式獲取對(duì)象而且忽略中斷,完畢synchronized語(yǔ)義。

在AQS類中的源代碼例如以下:

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

該函數(shù)主要完畢的邏輯例如以下:
1)首先調(diào)用tryAcquire(arg)函數(shù)嘗試獲取;
嘗試更改狀態(tài)state的值,而且保證原子性。

在tryAcquire方法中使用了同步器提供的對(duì)state操作的方法。利用compareAndSet保證僅僅有一個(gè)線程可以對(duì)狀態(tài)進(jìn)行成功改動(dòng),而沒(méi)有成功改動(dòng)的線程將進(jìn)入sync隊(duì)列排隊(duì)。

值得注意的是這個(gè)函數(shù)在AQS中并沒(méi)有實(shí)現(xiàn),而是在其繼承子類中實(shí)現(xiàn)(比方在ReentrantLock類中的內(nèi)部類中NonfairSync和FairSync中均實(shí)現(xiàn)了這種方法)。

當(dāng)獲取成功時(shí),就會(huì)返回true,這時(shí)源代碼中的if語(yǔ)句就會(huì)直接執(zhí)行if(0),也就是不滿足執(zhí)行條件。

2)假設(shè)獲取不到,將當(dāng)前線程構(gòu)造成節(jié)點(diǎn)Node并加入sync隊(duì)列。
進(jìn)入隊(duì)列的每一個(gè)線程都是一個(gè)節(jié)點(diǎn)Node,從而形成了一個(gè)雙向隊(duì)列,相似CLH隊(duì)列,這樣做的目的是線程間的通信會(huì)被限制在較小規(guī)模(也就是兩個(gè)節(jié)點(diǎn)左右)。

3)再次嘗試獲取。假設(shè)沒(méi)有獲取到那么將當(dāng)前線程從線程調(diào)度器上摘下。進(jìn)入等待狀態(tài)。


使用LockSupport將當(dāng)前線程unpark,關(guān)于LockSupport興許會(huì)具體介紹。

看看addWaiter()方法的邏輯:

凝視解釋的是:通過(guò)給定的模式和當(dāng)前線程創(chuàng)建同步隊(duì)列結(jié)點(diǎn)。

源代碼例如以下:

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// 高速嘗試在尾部加入Node pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}//enq(node);return node; }//Inserts node into queue, initializing if necessary. See picture above. private Node enq(final Node node) {//死循環(huán)直至returnfor (;;) {Node t = tail;//必須初始化的步驟if (t == null) { if (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}} }

上面的邏輯解釋:
1)使用當(dāng)前線程構(gòu)造Node;
對(duì)于一個(gè)新創(chuàng)建的節(jié)點(diǎn)須要做的是:將新節(jié)點(diǎn)前驅(qū)節(jié)點(diǎn)指向尾節(jié)點(diǎn)(current.prev = tail)。尾節(jié)點(diǎn)指向它(tail = current),原有的尾節(jié)點(diǎn)的后繼節(jié)點(diǎn)指向它(t.next = current)而這些操作要求是原子的。上面的操作是利用尾節(jié)點(diǎn)的設(shè)置來(lái)保證的,也就是compareAndSetTail來(lái)完畢的。

2)先行嘗試在隊(duì)尾加入;
假設(shè)尾節(jié)點(diǎn)已經(jīng)有了。然后做例如以下操作:
(1)分配引用pred指向尾節(jié)點(diǎn)。
(2)調(diào)用compareAndSetTail(pred, node)將新節(jié)點(diǎn)更新為尾節(jié)點(diǎn);
(3)直接return,返回新插入的結(jié)點(diǎn)。

3)假設(shè)隊(duì)尾加入失敗或者是第一個(gè)入隊(duì)的節(jié)點(diǎn)。
假設(shè)是第1個(gè)節(jié)點(diǎn),也就是sync隊(duì)列沒(méi)有初始化,那么會(huì)進(jìn)入到enq這種方法,進(jìn)入的線程可能有多個(gè),或者說(shuō)在addWaiter中沒(méi)有成功入隊(duì)的線程都將進(jìn)入enq這種方法。

enq(node)函數(shù)的邏輯是確保進(jìn)入的Node都會(huì)有機(jī)會(huì)順序的加入到sync隊(duì)列中,而加入的過(guò)程例如以下:
(1)假設(shè)尾節(jié)點(diǎn)為空,那么原子化的分配一個(gè)頭節(jié)點(diǎn)。并將尾節(jié)點(diǎn)指向頭節(jié)點(diǎn),這一步是初始化;
(2)然后是反復(fù)在addWaiter中做的工作,可是在一個(gè)for (;;)的循環(huán)中,直到當(dāng)前節(jié)點(diǎn)入隊(duì)為止。

至此。addWaiter()方法的邏輯分析完畢。接下來(lái)就是分析(final Node node, int arg) 方法的邏輯。

進(jìn)入sync隊(duì)列之后。接下來(lái)就是要進(jìn)行鎖的獲取,或者說(shuō)是訪問(wèn)控制了。僅僅有一個(gè)線程可以在同一時(shí)刻繼續(xù)的執(zhí)行。而其它的進(jìn)入等待狀態(tài)。而每一個(gè)線程都是一個(gè)獨(dú)立的個(gè)體,它們自省的觀察。當(dāng)條件滿足的時(shí)候(自己的前驅(qū)是頭結(jié)點(diǎn)而且原子性的獲取了狀態(tài))。那么這個(gè)線程可以繼續(xù)執(zhí)行。

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {//獲取前驅(qū)結(jié)點(diǎn)final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);} }

上述邏輯主要包括:
1. 獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn);
須要獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)。而頭結(jié)點(diǎn)所相應(yīng)的含義是當(dāng)前站有鎖且正在執(zhí)行。
2. 當(dāng)前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)而且可以獲取狀態(tài),代表該當(dāng)前節(jié)點(diǎn)占有鎖;
假設(shè)滿足上述條件,那么代表可以占有鎖。根據(jù)節(jié)點(diǎn)對(duì)鎖占有的含義,設(shè)置頭結(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn)。
3. 否則進(jìn)入等待狀態(tài)。
假設(shè)沒(méi)有輪到當(dāng)前節(jié)點(diǎn)執(zhí)行。那么將當(dāng)前線程從線程調(diào)度器上摘下。也就是進(jìn)入等待狀態(tài)。

這里針對(duì)acquire做一下總結(jié):
1. 狀態(tài)的維護(hù);
須要在鎖定時(shí)。須要維護(hù)一個(gè)狀態(tài)(int類型)。而對(duì)狀態(tài)的操作是原子和非堵塞的,通過(guò)同步器提供的對(duì)狀態(tài)訪問(wèn)的方法對(duì)狀態(tài)進(jìn)行操縱,而且利用compareAndSet來(lái)確保原子性的改動(dòng)。
2. 狀態(tài)的獲取;
一旦成功的改動(dòng)了狀態(tài),當(dāng)前線程或者說(shuō)節(jié)點(diǎn),就被設(shè)置為頭節(jié)點(diǎn)。
3. sync隊(duì)列的維護(hù)。


在獲取資源未果的過(guò)程中條件不符合的情況下(不該自己,前驅(qū)節(jié)點(diǎn)不是頭節(jié)點(diǎn)或者沒(méi)有獲取到資源)進(jìn)入睡眠狀態(tài),停止線程調(diào)度器對(duì)當(dāng)前節(jié)點(diǎn)線程的調(diào)度。


這時(shí)引入的一個(gè)釋放的問(wèn)題,也就是說(shuō)使睡眠中的Node或者說(shuō)線程獲得通知的關(guān)鍵,就是前驅(qū)節(jié)點(diǎn)的通知,而這一個(gè)過(guò)程就是釋放。釋放會(huì)通知它的后繼節(jié)點(diǎn)從睡眠中返回準(zhǔn)備執(zhí)行。
下面的流程圖基本描寫敘述了一次acquire所須要經(jīng)歷的過(guò)程:

如上圖所看到的,當(dāng)中的判定退出隊(duì)列的條件,判定條件是否滿足和休眠當(dāng)前線程就是完畢了自旋spin的過(guò)程。

2)release(int arg)

首先看看Javadoc的定義:

以獨(dú)占模式釋放對(duì)象。假設(shè) tryRelease(int) 返回 true。則通過(guò)消除一個(gè)或多個(gè)線程的堵塞來(lái)實(shí)現(xiàn)此方法。可以使用此方法來(lái)實(shí)現(xiàn) Lock.unlock() 方法
源代碼例如以下:

public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false; }

在unlock方法的實(shí)現(xiàn)中。使用了同步器的release方法。

相對(duì)于在之前的acquire方法中可以得出調(diào)用acquire,保證可以獲取到鎖(成功獲取狀態(tài)),而release則表示將狀態(tài)設(shè)置回去,也就是將資源釋放,或者說(shuō)將鎖釋放。

上述邏輯主要包括:
1)嘗試釋放狀態(tài)。
tryRelease()函數(shù)可以保證原子化的將狀態(tài)設(shè)置回去。當(dāng)然須要使用compareAndSet來(lái)保證。假設(shè)釋放狀態(tài)成功過(guò)之后。將會(huì)進(jìn)入后繼節(jié)點(diǎn)的喚醒過(guò)程。
2. 喚醒當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)所包括的線程。
通過(guò)LockSupport的unpark方法將休眠中的線程喚醒,讓其繼續(xù)acquire狀態(tài)。

private void unparkSuccessor(Node node) {// 將狀態(tài)設(shè)置為同步狀態(tài)int ws = node.waitStatus;if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 獲取當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn),假設(shè)滿足狀態(tài),那么進(jìn)行喚醒操作 // 假設(shè)沒(méi)有滿足狀態(tài),從尾部開始找尋符合要求的節(jié)點(diǎn)并將其喚醒 Node s = node.next; if (s == null || s.waitStatus &gt; 0) {s = null;for (Node t = tail; t != null &amp;&amp; t != node; t = t.prev)if (t.waitStatus &lt;= 0)s = t;}if (s != null)LockSupport.unpark(s.thread); }

上述邏輯主要包括,該方法取出了當(dāng)前節(jié)點(diǎn)的next引用,然后對(duì)其線程(Node)進(jìn)行了喚醒。這時(shí)就僅僅有一個(gè)或合理個(gè)數(shù)的線程被喚醒。被喚醒的線程繼續(xù)進(jìn)行對(duì)資源的獲取與爭(zhēng)奪。

回想整個(gè)資源的獲取和釋放過(guò)程:
1)在獲取時(shí),維護(hù)了一個(gè)sync隊(duì)列。每一個(gè)節(jié)點(diǎn)都是一個(gè)線程在進(jìn)行自旋,而根據(jù)就是自己是否是首節(jié)點(diǎn)的后繼而且可以獲取資源;
2)在釋放時(shí)。僅僅須要將資源還回去。然后通知一下后繼節(jié)點(diǎn)并將其喚醒。

這里須要注意,隊(duì)列的維護(hù)(首節(jié)點(diǎn)的更換)是依靠消費(fèi)者(獲取時(shí))來(lái)完畢的,也就是說(shuō)在滿足了自旋退出的條件時(shí)的一刻。這個(gè)節(jié)點(diǎn)就會(huì)被設(shè)置成為首節(jié)點(diǎn)。

至此AQS基本的兩個(gè)函數(shù)分析完畢。這兩個(gè)函數(shù)也是lock()函數(shù)和unlock()函數(shù)的核心。

轉(zhuǎn)載于:https://www.cnblogs.com/wzjhoutai/p/7380804.html

總結(jié)

以上是生活随笔為你收集整理的J.U.C--locks--AQS分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。