一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java并發編程之JUC并發核心AQS同步隊列原理剖析

Java并發編程之JUC并發核心AQS同步隊列原理剖析

2022-01-13 00:57沒頭腦遇到不高興 Java教程

AbstractQueuedSynchronizer 簡稱 AQS,可能我們幾乎不會直接去使用它,但它卻是 JUC 的核心基礎組件,支撐著 java 鎖和同步器的實現,大神 Doug Lea 在設計 JUC 包時希望能夠抽象一個基礎且通用的組件以支撐上層模塊的實現,AQS 應運而生

 

一、AQS介紹

隊列同步器AbstractQueuedSynchronizer(簡稱AQS),AQS定義了一套多線程訪問共享資源的同步器框架,是用來構建鎖或者其他同步組件的基礎框架,是一個依賴狀態(state)的同步器。Java并發編程的核心在java.util.concurrent(簡稱juc)包,而juc包的大部分工具都是以AQS為基礎進行構建的,例如Semaphore、ReentranLock、CountDownLatch、CyclicBarrier等,它的作者是鼎鼎大名的Doug Lea。

AQS具備特性

  • 阻塞等待隊列
  • 共享/獨占
  • 公平/非公平
  • 可重入
  • 允許中斷

它維護了一個volatile int state(代表共享資源)和一個FIFO線程等待隊列(多線程爭用資源被阻塞時會進入此隊列)。state的訪問方式有三種:

  • getState() 獲取state
  • setState() 設置state
  • compareAndSetState() 通過CAS的方式設置state值

AQS有兩種資源共享方式:Exclusive(獨占式) 和 Share(共享式)。所謂獨占式是指依據AQS中的state控制狀態,只有一個線程能夠進行工作(其它參與調度的線程會進入阻塞狀態,如ReentrantLock);共享式是指,依據AQS中的state控制狀態,可以有多個滿足條件的線程同時執行(如Semaphore/CountDownLatch)。 

AQS定義兩種隊列

  • 同步等待隊列(基于雙向鏈表實現)
  • 條件等待隊列(基于單向鏈表實現)

不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實現時只需要實現共享資源state的獲取與釋放方式即可,至于具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。一般通過定義內部類Sync繼承AQS將同步器所有調用都映射到Sync對應的方法。

自定義同步器實現時主要實現以下幾種方法:

  • isHeldExclusively():該線程是否正在獨占資源,如果返回true,則表示當前線程正在獨占資源。只有用到condition才需要去實現它。
  • tryAcquire(int):獨占方式。嘗試獲取資源,成功則返回true,失敗則返回false。
  • tryRelease(int):獨占方式。嘗試釋放資源,成功則返回true,失敗則返回false。
  • tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩余可用資源;正數表示成功,且有剩余資源。
  • tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放后允許喚醒后續等待結點返回true,否則返回false。

實現自定義同步組件時,將會調用同步器提供的模板方法,這些(部分)模板方法與描述如下。同步器提供的模板方法基本上分為3類:獨占式獲取與釋放同步狀態、共享式獲取與釋放同步狀態和查詢同步隊列中的等待線程情況。自定義同步組件將使用同步器提供的模板方法來實現自己的同步語義。

  • acquire(int arg):獨占式獲取同步狀態,如果當前線程獲取同步狀態成功,則由該方法返回,否則,將會進入同步隊列等待,該方法將會調用可重寫的tryAcquire(int arg)方法;
  • acquireInterruptibly(int arg):與acquire(int arg)相同,但是該方法響應中斷,當前線程為獲取到同步狀態而進入到同步隊列中,如果當前線程被中斷,則該方法會拋出InterruptedException異常并返回;
  • tryAcquireNanos(int arg,long nanos):超時獲取同步狀態,如果當前線程在nanos時間內沒有獲取到同步狀態,那么將會返回false,已經獲取則返回true;
  • acquireShared(int arg):共享式獲取同步狀態,如果當前線程未獲取到同步狀態,將會進入同步隊列等待,與獨占式的主要區別是在同一時刻可以有多個線程獲取到同步狀態;
  • acquireSharedInterruptibly(int arg):共享式獲取同步狀態,響應中斷;
  • tryAcquireSharedNanos(int arg, long nanosTimeout):共享式獲取同步狀態,增加超時限制;
  • release(int arg):獨占式釋放同步狀態,該方法會在釋放同步狀態之后,將同步隊列中第一個節點包含的線程喚醒;
  • releaseShared(int arg):共享式釋放同步狀態;

 

二、AQS中的隊列

 

1、同步等待隊列

AQS當中的同步等待隊列也稱CLH隊列,CLH隊列是Craig、Landin、Hagersten三人發明的一種基于雙向鏈表數據結構的隊列,是FIFO先入先出線程等待隊列,Java中的CLH隊列是原CLH隊列的一個變種,線程由原自旋機制改為阻塞機制。

這種結構的特點是每個數據結構都有兩個指針,分別指向直接的后繼節點和直接前驅節點。所以雙向鏈表可以從任意一個節點開始很方便的訪問前驅和后繼。每個 Node 其實是由線程封裝,當線程爭搶鎖失敗后會封裝成 Node 加入到 ASQ 隊列中去;當獲取鎖的線程釋放鎖以后,會從隊列中喚醒一個阻塞的節點線程 。

Java并發編程之JUC并發核心AQS同步隊列原理剖析

 

2、條件等待隊列

條件等待隊列是單向鏈表實現的,此時Node(下面會介紹)中pre和next都為null。Condition是一個多線程間協調通信的工具類,使得某個、或者某些線程一起等待某個條件(Condition),只有當該條件具備時,這些等待線程才會被喚醒,從而重新爭奪鎖。

Java并發編程之JUC并發核心AQS同步隊列原理剖析

 

3、AQS隊列節點Node

同步隊列中的節點(Node)用來保存獲取同步狀態失敗的線程引用、等待狀態以及前驅和后繼節點,節點的屬性類型與名稱等,Node類基本屬性定義如下所示,它是在AbstractQueuedSynchronizer中的一個內部類。

注意:如果Node在條件隊列當中,Node必須是獨占模式,不能是共享模式。

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;
}

Node pre:前驅節點,當前節點加入到同步隊列中被設置(尾部添加)

Node next:后繼節點

Thread thread:節點同步狀態的線程

Node nextWaiter:等待隊列中的后繼節點,如果當前節點是共享的,那么這個字段是一個SHARED常量,也就是說節點類型(獨占和共享)和等待隊列中的后繼節點共用同一個字段

int waitStatus:等待狀態,標記當前節點的信號量狀態 (1,0,-1,-2,-3)5種狀態,使用CAS更改狀態,volatile保證線程可見性,高并發場景下,即被一個線程修改后,狀態會立馬讓其他線程可見,五種狀態分別為:

  • CANCELLED,值為1,在同步隊列中等待的線程等待超時或者被中斷,需要從同步隊列中取消等待,節點進入該狀態后將不會變化
  • SIGNAL,值為-1,后繼節點的線程處于等待狀態,而當前的節點如果釋放了同步狀態或者被取消,將會通知后繼節點,使后繼節點的線程得以運行。
  • CONDITION,值為-2,節點在等待隊列中,節點的線程等待在Condition上,當其他線程對Condition調用了signal()方法后,該節點會從等待隊列中轉移到同步隊列中,加入到同步狀態的獲取中
  • PROPAGATE ,值為-3,表示下一次共享式同步狀態獲取將會被無條件地傳播下去
  • INITIAL,值為0,初始狀態

 

三、同步隊列源碼分析

 

1、同步隊列分析

同步器擁有首節點(head)和尾節點(tail),沒有成功獲取同步狀態的線程將會成為節點加入該隊列的尾部,同步隊列的基本結構如圖所示。

Java并發編程之JUC并發核心AQS同步隊列原理剖析

同步器包含了兩個節點類型的引用,一個指向頭節點,而另一個指向尾節點。同步器提供了一個基于CAS的設置尾節點的方法:compareAndSetTail(Node expect,Node update),它需要傳遞當前線程“認為”的尾節點和當前節點,只有設置成功后,當前節點才正式與之前的尾節點建立關聯。 涉及兩個變化:

  • 1. 新的線程封裝成 Node 節點追加到同步隊列中,設置 prev 節點以及修改當前節點的前置節點的 next 節點指向自己
  • 2. 通過 CAS 講 tail 重新指向新的尾部節點

head節點表示獲取鎖成功的節點,當頭結點在釋放同步狀態時,會喚醒后繼節點,如果后繼節點獲得鎖成功,會把自己設置為頭結點,節點的變化過程如下

同步隊列遵循FIFO,首節點是獲取同步狀態成功的節點,首節點的線程在釋放同步狀態時,將會喚醒后繼節點,而后繼節點將會在獲取同步狀態成功時將自己設置為首節點,該過程如下圖所示。涉及兩個變化:

  • 1. 修改 head 節點指向下一個獲得鎖的節點
  • 2. 新的獲得鎖的節點,將 prev 的指針指向 null

Java并發編程之JUC并發核心AQS同步隊列原理剖析

設置首節點是通過獲取同步狀態成功的線程來完成的,由于只有一個線程能夠成功獲取到同步狀態,因此設置頭節點的方法并不需要使用CAS來保證,它只需要將首節
點設置成為原首節點的后繼節點并斷開原首節點的next引用即可。

 

2、同步隊列――獨占模式源碼分析

acquire方法(獨占獲取)源碼分析

通過調用同步器的acquire(int arg)方法可以獲取同步狀態,該方法對中斷不敏感,也就是由于線程獲取同步狀態失敗后進入同步隊列中,后續對線程進行中斷操作時,線程不會從同步隊列中移出。

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

上面方法中首先調用自定義同步器實現的tryAcquire(int arg)方法(重寫該方法),該方法保證線程安全的獲取同步狀態,如果同步狀態獲取失敗,則構造同步節點(獨占式Node.EXCLUSIVE,同一時刻只能有一個線程成功獲取同步狀態)并通過addWaiter(Node node)方法將該節點加入到同步隊列的尾部,最后調用acquireQueued(Node node,int arg)方法,使得該節點以“死循環”的方式獲取同步狀態。如果獲取不到則阻塞節點中的線程,而被阻塞線程的喚醒主要依靠前驅節點的出隊或阻塞線程被中斷來實現。

下面看下addWaiter方法的實現:把當前線程構建為Node節點;判斷尾結點是否為空,通過CAS的方式將當前節點放到隊列尾部;如果尾結點不為空或者前面CAS插入尾結點失敗,調用enq方法,通過自旋的方式插入尾結點。

private Node addWaiter(Node mode) {
	// 1. 將當前線程構建成Node類型
	Node node = new Node(Thread.currentThread(), mode);
	// Try the fast path of enq; backup to full enq on failure
	Node pred = tail;
	// 2. 1當前尾節點是否為null?
	if (pred != null) {
		// 2.2 將當前節點尾插入的方式
		node.prev = pred;
		// 2.3 CAS將節點插入同步隊列的尾部
		if (compareAndSetTail(pred, node)) {
			pred.next = node;
			return node;
		}
	}
	enq(node);
	return node;
}

下面看下enq方法的實現:判斷尾結點是否為空,如果為空,則通過CAS的方式創建一個空的頭結點(Thread為空),并將尾結點也指向頭結點;如果尾結點不為空或者上面CAS創建頭結點失敗,將當前隊列的前驅指針指向原來的尾結點,通過CAS的方式將當前節點放到隊列尾部,將原來尾結點的后繼指針指向當前節點;如果前面都失敗了,進行下一次循環。當前線程構造的node節點通過addWaiter方法執行入隊之后,其waitStatus為0,頭結點的waitStatus也是0,此時是下面這種結構

Java并發編程之JUC并發核心AQS同步隊列原理剖析

private Node enq(final Node node) {
	for (;;) {
		Node t = tail;
		if (t == null) { // Must initialize
			//隊列為空需要初始化,創建空的頭節點
			if (compareAndSetHead(new Node()))
				tail = head;
		} else {
			node.prev = t;
			//set尾部節點
			if (compareAndSetTail(t, node)) {//當前節點置為尾部
				t.next = node; //前驅節點的next指針指向當前節點
				return t;
			}
		}
	}
}

通過addWaiter 方法把線程添加到鏈表后, 會接著把 Node 作為參數傳遞給acquireQueued 方法,去競爭鎖

  • 1. 獲取當前節點的 prev 節點
  • 2. 如果 prev 節點為 head 節點,那么它就有資格去爭搶鎖,調用 tryAcquire 搶占鎖
  • 3. 搶占鎖成功以后,把獲得鎖的節點設置為 head ,并且移除原來的初始head節點,通過setHead方法和p.next=null來將新的head(獲取鎖的線程節點)與原head斷開連接,并將新的head的thread設為null。由此可見head節點的waitStatus都為0
  • 4. 如果當前節點的前驅不是head或者當前節點是head但是獲取鎖失敗,則根據前驅節點的waitStatus(SIGNAL)決定是否需要掛起線程
/**
 * 已經在隊列當中的Thread節點,準備阻塞等待獲取鎖
 */
final boolean acquireQueued(final Node node, int arg) {
	boolean failed = true;
	try {
		boolean interrupted = false;
		for (;;) {//死循環
			final Node p = node.predecessor();//找到當前結點的前驅結點
			if (p == head && tryAcquire(arg)) {//如果前驅結點是頭結點,才tryAcquire,其他結點是沒有機會tryAcquire的。
				setHead(node);//獲取同步狀態成功,將當前結點設置為頭結點。
				p.next = null; // help GC
				failed = false;
				return interrupted;
			}
			/**
			 * 如果前驅節點不是Head,通過shouldParkAfterFailedAcquire判斷是否應該阻塞
			 * 前驅節點信號量為-1,當前線程可以安全被parkAndCheckInterrupt用來阻塞線程
			 */
			if (shouldParkAfterFailedAcquire(p, node) &&
					parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

下面是setHead方法的實現

private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

在acquireQueued(final Node node,int arg)方法中,當前線程在“死循環”中嘗試獲取同步狀態,而只有前驅節點是頭節點才能夠嘗試獲取同步狀態,這是為什么?原因有兩個,如下。

  • 第一,頭節點是成功獲取到同步狀態的節點,而頭節點的線程釋放了同步狀態之后,將會喚醒其后繼節點,后繼節點的線程被喚醒后需要檢查自己的前驅節點是否是頭節點。
  • 第二,維護同步隊列的FIFO原則。該方法中,節點自旋獲取同步狀態的行為如圖所示

Java并發編程之JUC并發核心AQS同步隊列原理剖析

如果前驅節點不是Head,通過shouldParkAfterFailedAcquire判斷是否應該阻塞:如果前驅節點waitStatus為-1(SIGNAL的狀態),當前線程可以安全的被parkAndCheckInterrupt用來阻塞線程;通過循環掃描鏈表把 CANCELLED 狀態的節點移除;如果前驅節點waitStatus不是-1,則通過CAS將前驅節點的waitStatus改為-1。

第一次循環進入shouldParkAfterFailedAcquire方法時head節點為0,會將其改為SIGNAL,此時會返回false,那么外層的方法acquireQueued方法會執行第二次循環進入shouldParkAfterFailedAcquire方法,此時會返回true,當前線程可以被阻塞,則調用parkAndCheckInterrupt()方法阻塞當前線程,其底層調用的是UnSafe類里面的park方法。

shouldParkAfterFailedAcquire方法會將前驅節點的waitStatus改為SIGNAL,因為只有前驅節點的狀態是SIGNAL后繼節點才可以被阻塞,次數除了tail節點的狀態是0,其他的都是-1。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	int ws = pred.waitStatus;
	if (ws == Node.SIGNAL)
		/*
		 * 若前驅結點的狀態是SIGNAL,意味著當前結點可以被安全地park
		 */
		return true;
	if (ws > 0) {
		/*
		 * 前驅節點狀態如果被取消狀態,將被移除出隊列
		 */
		do {
			node.prev = pred = pred.prev;
		} while (pred.waitStatus > 0);
		pred.next = node;
	} else {
		/*
		 * 當前驅節點waitStatus為 0 or PROPAGATE狀態時
		 * 將其設置為SIGNAL狀態,然后當前結點才可以可以被安全地park
		 */
		compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	}
	return false;
}

通過分析acquireQueued方法可以得出結論:頭結點是獲取同步狀態成功的節點,頭結點的所有有效后繼節點線程都會被阻塞,釋放鎖后需要挨個喚醒頭結點的后續線程節點。

獨占式同步狀態獲取流程,也就是acquire(int arg)方法調用流程,如圖所示。

Java并發編程之JUC并發核心AQS同步隊列原理剖析

release方法(獨占釋放)源碼分析

當前線程獲取同步狀態并執行了相應邏輯之后,就需要釋放同步狀態,使得后續節點能夠繼續獲取同步狀態。通過調用同步器的release(int arg)方法可以釋放同步狀態,該方法在tryRelease方法釋放了同步狀態之后,會喚醒其后繼節點(進而使后繼節點重新嘗試獲取同步狀態)。

public final boolean release(int arg) {
	if (tryRelease(arg)) {//釋放一次鎖
		Node h = head;
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);//喚醒后繼結點
		return true;
	}
	return false;
}

該方法執行時,會喚醒頭節點的后繼節點線程,unparkSuccessor(Node node)方法底層使用UnSafe的unpark方法來喚醒處于等待狀態的線程。

private void unparkSuccessor(Node node) {
	//獲取wait狀態
	int ws = node.waitStatus;
	if (ws < 0)
		compareAndSetWaitStatus(node, ws, 0);// 將等待狀態waitStatus設置為初始值0
 
	/**
	 * 若后繼結點為空,或狀態為CANCEL(已失效),則從后尾部往前遍歷找到最前的一個處于正常阻塞狀態的結點
	 * 進行喚醒
	 */
	Node s = node.next;
	if (s == null || s.waitStatus > 0) {
		s = null;
		for (Node t = tail; t != null && t != node; t = t.prev)
			if (t.waitStatus <= 0)
				s = t;
	}
	if (s != null)
		LockSupport.unpark(s.thread);//喚醒線程
}

上面方法中判斷node的后繼節點是空或者waitStatus是撤銷狀態,會從tail往前遍歷找到一個離node節點最近的節點,這是為什么呢? 原因在于上面acquire時調用的enq入隊方法:先compareAndSetTail(t, node)設置尾結點,然后t.next=node將前驅節點的next指針指向當前節點,如果t.next=node還沒有執行的話,鏈表還沒有建立完整,從前向后遍歷時會出現遍歷到t時找不到t的后繼節點,從后往前遍歷則不會出現這種情況。

private Node enq(final Node node) {
	for (;;) {
		Node t = tail;
		if (t == null) { // Must initialize
			//隊列為空需要初始化,創建空的頭節點
			if (compareAndSetHead(new Node()))
				tail = head;
		} else {
			node.prev = t;
			//set尾部節點
			if (compareAndSetTail(t, node)) {//當前節點置為尾部
				t.next = node; //前驅節點的next指針指向當前節點
				return t;
			}
		}
	}
}

分析了獨占式同步狀態獲取和釋放過程后,適當做個總結:在獲取同步狀態時,同步器維護一個同步隊列,獲取狀態失敗的線程都會被加入到隊列中(都是頭結點的后繼節點)并在隊列中進行自旋;移出隊列(或停止自旋)的條件是前驅節點為頭節點且成功獲取了同步狀態。在釋放同步狀態時,同步器調用tryRelease(int arg)方法釋放同步狀態,然后喚醒頭節點的后繼節點。

 

3、同步隊列――共享模式源碼分析

同步隊列共享模式與獨占模式異同點:

  • 共享模式跟獨占模式的相同點:獲取同步狀態失敗的線程會被包裝成node節點添加到隊列尾部
  • 共享模式跟獨占模式的不同點:獨占模式中同步狀態是獨占的,只有一個線程可以獲取到同步資源,因此釋放同步資源時會喚醒head節點后面的一個節點;而共享模式因為多個線程可以共享同步資源,所以喚醒線程時會喚醒head節點后面的所有有效節點。

acquireShared方法(共享獲取)源碼分析

acquireShared方法會先調用tryAcquireShared獲取同步狀態,如果返回值小于0表示獲取失敗,需要進行排隊;如果獲取成功,則可以向下執行。

public final void acquireShared(int arg) {
	if (tryAcquireShared(arg) < 0)//返回值小于0,獲取同步狀態失敗,排隊去;獲取同步狀態成功,直接返回去干自己的事兒。
		doAcquireShared(arg);
}

獲取失敗時會調用doAcquireShared方法進行排隊:

  • 先通過addWaiter方法入隊,上面已經介紹了,這里就不再重復分析了,唯一不同的就是添加的是一個共享模式的node

  • 判斷當前節點的前驅節點是否是head,如果是的話再次調用tryAcquireShared獲取同步資源,如果獲取成功,則將當前node設置為head并且喚醒等待的線程節點

  • 如果當前節點的前驅節點不是head或者是head但是獲取同步資源失敗,則跟上面的共享模式一樣調用shouldParkAfterFailedAcquire方法將node的前驅節點設置為SIGNAL(-1)狀態,然后阻塞當前線程。

    private void doAcquireShared(int arg) {
    	final Node node = addWaiter(Node.SHARED);//入隊
    	boolean failed = true;
    	try {
    		boolean interrupted = false;
    		for (;;) {
    			final Node p = node.predecessor();//前驅節點
    			if (p == head) {
    				int r = tryAcquireShared(arg); //非公平鎖實現,再嘗試獲取鎖
    				//state==0時tryAcquireShared會返回>=0(CountDownLatch中返回的是1)。
    				// state為0說明共享次數已經到了,可以獲取鎖了
    				if (r >= 0) {//r>0表示state==0,前繼節點已經釋放鎖,鎖的狀態為可被獲取
    					//這一步設置node為head節點設置node.waitStatus->Node.PROPAGATE,然后喚醒node.thread
    					setHeadAndPropagate(node, r);
    					p.next = null; // help GC
    					if (interrupted)
    						selfInterrupt();
    					failed = false;
    					return;
    				}
    			}
    			//前繼節點非head節點,將前繼節點狀態設置為SIGNAL,通過park掛起node節點的線程
    			if (shouldParkAfterFailedAcquire(p, node) &&
    					parkAndCheckInterrupt())
    				interrupted = true;
    		}
    	} finally {
    		if (failed)
    			cancelAcquire(node);
    	}
    }

    下面看下setHeadAndPropagate方法:

  • 調用setHead方法將當前節點設置head

  • 判斷如果需要執行喚醒,通過上面的分析這里會調用doReleaseShared執行喚醒

    /**
     * 把node節點設置成head節點,且Node.waitStatus->Node.PROPAGATE
     */
    private void setHeadAndPropagate(Node node, int propagate) {
    	Node h = head; //h用來保存舊的head節點
    	setHead(node);//head引用指向node節點
    	/* 這里意思有兩種情況是需要執行喚醒操作
    	 * 1.propagate > 0 表示調用方指明了后繼節點需要被喚醒
    	 * 2.頭節點后面的節點需要被喚醒(waitStatus<0),不論是老的頭結點還是新的頭結點
    	 */
    	if (propagate > 0 || h == null || h.waitStatus < 0 ||
    			(h = head) == null || h.waitStatus < 0) {
    		Node s = node.next;
    		if (s == null || s.isShared())//node是最后一個節點或者 node的后繼節點是共享節點
    			/* 如果head節點狀態為SIGNAL,喚醒head節點線程,重置head.waitStatus->0
    			 * head節點狀態為0(第一次添加時是0),設置head.waitStatus->Node.PROPAGATE表示狀態需要向后繼節點傳播
    			 */
    			doReleaseShared();
    	}
    }

    看下doReleaseShared方法的實現:

    判斷head!=null && head!=tail,然后判斷head的waitStatus如果是SIGNAL則會使用CAS的方式將其改為0,這里沒有直接改成PROPAGATE,是因為unparkSuccessor(h)中,如果ws < 0會設置為0,所以ws先設置為0,再設置為PROPAGATE,這里需要控制并發,因為入口有setHeadAndPropagate跟release兩個,避免兩次unpark。head狀態為SIGNAL,且成功設置為0之后喚醒head.next節點線程,此時head、head.next的線程都喚醒了,head.next會去競爭鎖,成功后head會指向獲取鎖的節點,也就是head發生了變化。head發生變化后h==head會不成立了,此時會重新循環,繼續喚醒重新獲取的新head的下一個節點。

    如果本身頭節點的waitStatus是0,將其設置為PROPAGATE狀態。意味著需要將狀態向后一個節點傳播。

    最后判斷如果h==head,即已經沒有要喚醒的節點了,跳出循環向下執行。如果h!=head,則說明head指針發生了變化,head已經指向了新喚醒的線程node,繼續執行下次循環,獲取新的head,喚醒head的后續節點。

     private void doReleaseShared() {
    	for (;;) {
    		Node h = head;
    		if (h != null && h != tail) {
    			int ws = h.waitStatus;
    			if (ws == Node.SIGNAL) {//head是SIGNAL狀態
    				/* head狀態是SIGNAL,重置head節點waitStatus為0,這里不直接設為Node.PROPAGATE,
    				 * 是因為unparkSuccessor(h)中,如果ws < 0會設置為0,所以ws先設置為0,再設置為PROPAGATE
    				 * 這里需要控制并發,因為入口有setHeadAndPropagate跟release兩個,避免兩次unpark
    				 */
    				if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
    					continue; //設置失敗,重新循環
    				/* head狀態為SIGNAL,且成功設置為0之后,喚醒head.next節點線程
    				 * 此時head、head.next的線程都喚醒了,head.next會去競爭鎖,成功后head會指向獲取鎖的節點,
    				 * 也就是head發生了變化。看最底下一行代碼可知,head發生變化后會重新循環,繼續喚醒head的下一個節點
    				 */
    				unparkSuccessor(h);
    				/*
    				 * 如果本身頭節點的waitStatus是出于重置狀態(waitStatus==0)的,將其設置為“傳播”狀態。
    				 * 意味著需要將狀態向后一個節點傳播
    				 */
    			}
    			else if (ws == 0 &&
    					!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
    				continue;                // loop on failed CAS
    		}
    		if (h == head) //如果head變了,重新循環
    			break;
    	}
    }

    releaseShared方法(共享釋放)源碼分析

    調用tryReleaseShared方法釋放資源成功時會調用doReleaseShared方法執行喚醒邏輯,上面已經分析過了。

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

到此這篇關于Java并發編程之JUC并發核心AQS同步隊列原理剖析的文章就介紹到這了,更多相關JUC并發核心AQS同步隊列內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/u012988901/article/details/112251913

延伸 · 閱讀

精彩推薦
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
主站蜘蛛池模板: 欧美日韩中文字幕久久伊人 | 男生操女生漫画 | 久久国产视频网站 | 毛片亚洲毛片亚洲毛片 | 欧美生活一级片 | 欧美日韩一二三区免费视频观看 | 91污污视频 | www.亚洲视频 | 亚洲 欧美 中文 日韩 另类 | 国产精品久久久久久久久久久久久久 | 娇妻与公陈峰姚瑶小说在线阅读 | 亚洲男女在线 | 3d肉浦团在线观看 | 天天爱天天做天天爽天天躁 | 午夜影院0606免费 | 精品无人区乱码1区2区3区免费 | 成年女人免费 | 国产精品久久国产精品99 | 91精品啪在线观看国产线免费 | china精品对白普通话 | 国产精品久久久久久久久久久搜索 | 欧美日本一道高清免费3区 欧美人做人爱a全程免费 | 香蕉精品国产高清自在自线 | 亚洲美色综合天天久久综合精品 | 喜爱夜蒲2三级做爰 | bl文全肉高h湿被灌尿 | 四虎影院永久网站 | 九九大香尹人视频免费 | 2020韩国三级理论在线观看 | 久久囯产精品777蜜桃传媒 | 国产亚洲小视频 | 涩涩五月天 | 特黄特色大片免费视频大全 | 9自拍视频在线观看 | 亚洲国产综合久久久无码色伦 | 国产麻豆精品免费视频 | 国产精品拍拍拍福利在线观看 | 91精品综合久久久久久五月天 | 欧美日韩1区2区 | 国产乱码在线精品可播放 | 五月天国产精品 |