併發編程(四):AbstractQueuedSynchronizer源碼分析

一,AQS

    1,AQS是JUC的核心功能組件,主要通過FIFO雙向鏈表特性對進行park()和unpark()操作來實現線程阻塞和線程喚醒。AQS主要提供了兩種功能,獨佔和共享。

            * 獨佔鎖:獨佔鎖,每次只能有一個線程持有鎖,比如ReentrantLock 就是以獨佔方式實現的互斥鎖

            * 共享鎖:允許多個線程同時持有鎖,併發訪問共享資源,比如ReentrantReadWriteLock.ReadLock,CountDownLatch

    2,類圖

        * 從主體結構看,AQS繼承自 AbstractOwnableSynchronizer(獲取鎖的當前線程獲取和設置),並實現了序列化接口。AQS內部定義了Node和ConditionObject兩個內部類。其中Node類是構建AQS的FIFO雙向鏈表的基礎數據結構;ConditionObject實現了Condition接口,是基於AQS進行線程通信的基本類。

        * 其他所有JUC內涉及併發處理的類,基本的處理邏輯都是基於定義內部類Sync並繼承AQS類,對AQS內部部分接口進行重寫,變形爲各自的業務需求;如:ReentrantLock,ReentrantReadWriteLock,CountDownLatch,Semaphore等等...

二,AQS的內部實現

        1,,AQS是一個FIFO(First Inner First Outer)的雙向鏈表,這種結構的特點是每個節點都有兩個指針,分別指向當前節點的前置節點和後置節點。所以可以從任何一個節點很方便的訪問前後節點,每一個節點由AQS內部類Node表示。在存在線程競爭時,每一個未競爭到鎖的線程都會被封裝成Node節點存儲到AQS同步隊列中。噹噹前獲取到鎖的線程釋放鎖後,會從隊列中喚醒下一個阻塞的節點,以此類推。

        2,Node結構如下:

            * Node節點的幾種狀態

// 表示共享鎖
static final Node SHARED = new Node();
// 表示獨佔鎖
static final Node EXCLUSIVE = null;
// 表示當前節點失效
static final int CANCELLED =  1;
// 節點初始狀態,表示在FIFO雙向鏈表構成的同步隊列中等待
static final int SIGNAL    = -1;
// Condition單向鏈表隊列狀態
static final int CONDITION = -2;
// 
static final int PROPAGATE = -3;

        3,AQS阻塞隊列添加節點

            * 新的線程封裝成Node節點添加到AQS同步隊列中,設置前置節點(prev)爲之前的tail節點,並將tail節點的next節點指向當前節點

            * 通過CAS將tail節點指向當前節點

            * 此處注意是先設置的prev節點,所以在後續遍歷處理的時候,是從後往前遍歷,就是因爲這個原因。因爲從前往後,可能存在next指向命令並沒有執行

        4,AQS阻塞隊列釋放節點

            * 當存在線程釋放鎖後,AQS的頭結點(head)會獲取鎖,並被AQS阻塞隊列釋放。

            * 修改head節點爲head指向的next節點,即下一個獲得鎖的節點,此處CAS不需要加鎖,因爲是加鎖的線程完成的

            * 修改新head節點的prev節點,將prev的指針指向null

 三,AQS獨佔鎖源碼分析

        * AQS內部沒有公平鎖和非公平鎖的劃分,所謂公平鎖和非公平鎖是部分業務代碼內部實現(如ReentrantLock)

    1,從一段獨佔鎖代碼開始

package com.gupao.concurrent;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 獨佔鎖演示
 * @author pj_zhang
 * @create 2019-10-01 14:28
 **/
public class ExclusiveTest {

    // 定義全局重入鎖對象
    private static final ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            // 遍歷並啓動100道線程
            Thread thread = new Thread(() -> {
                try {
                    // 線程內部加鎖並沉睡業務秒數,模擬獲取到鎖後不立即釋放
                    reentrantLock.lock();
                    Thread.sleep(3000);
                    System.out.println(
                            Thread.currentThread().getName() + "獲取鎖,執行時間: "
                                    + System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // finally塊中釋放鎖
                    reentrantLock.unlock();
                }
            }, "THREAD_" + i);
            thread.start();
            System.out.println(thread.getName() + "啓動成功...");
        }
    }
}

        * 從代碼中我們可以看到,100道線程已經全部啓動,並以三秒的頻率依次執行,那這裏面就會有兩個疑問

        * 已經啓動但是被加鎖沒有立即執行的線程是以什麼樣的方式存在?

        * 獲得鎖的線程執行完成後,如何通知未獲得鎖的線程進行線程爭搶並執行?

    2,嘗試獲取鎖

        2.1,不帶時間獲取鎖

            * tryAcquire(int arg)

// 代碼內部直接拋異常,說明該部分代碼依託子類實現
// AQS的子類實現在各個鎖業務代碼中處理,後續會逐一分析
protected boolean tryAcquire(int arg) {
	throw new UnsupportedOperationException();
}

        2.2,帶時間獲取鎖 -- 該部分屬於底層類,後續業務代碼調用不再分析

            * tryAcquireNanos(int arg, long nanosTimeout)

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
		throws InterruptedException {
    // 判斷當前線程是否已經被中斷
    // 線程中斷,直接拋出異常
	if (Thread.interrupted())
		throw new InterruptedException();
    // 通過 tryAcquire(arg) 先直接嘗試獲取鎖,獲取到直接返回true
    // tryAcquire(arg) 需要在子類中實現,此處已經不做具體分析
    // 沒有獲取到後,通過 doAcquireNanos 進行限時時間內獲取
	return tryAcquire(arg) ||
		doAcquireNanos(arg, nanosTimeout);
}

            * doAcquireNanos(int arg, long nanosTimeout)

private boolean doAcquireNanos(int arg, long nanosTimeout)
		throws InterruptedException {
	if (nanosTimeout <= 0L)
		return false;
	// 獲取當前自旋結束時間,通過當前納秒數+超時納秒數
	final long deadline = System.nanoTime() + nanosTimeout;
	// 封裝當前線程爲Node節點,並添加的AQS同步隊列隊尾
	// 此處注意Node節點的waitStatus狀態爲EXCLUSIVE,即表示獨佔
	final Node node = addWaiter(Node.EXCLUSIVE);
	// 是否獲取到鎖標誌位
	boolean failed = true;
	try {
		for (;;) {
			// 獲取當前節點的上一個節點
			final Node p = node.predecessor();
			// 如果上一個節點爲head節點,並且繼續嘗試獲取鎖成功,則當前線程獲取到鎖
			// FIFO隊列默認存在一個head節點(不存在則第一次會創建),該頭結點爲空
			if (p == head && tryAcquire(arg)) {
				// 獲取到鎖後,將當前節點置爲頭結點,並清空頭節點內容
				setHead(node);
				// 將頭節點的next指向置爲空,處理後則p節點掛空,會被GC回收
				p.next = null; // help GC
				// 設置failed狀態爲false,說明競爭鎖成功
				failed = false;
				return true;
			}
			// 如果沒有獲取到鎖,則用過期時間減去當前時間,判斷剩餘時間是否合法
			nanosTimeout = deadline - System.nanoTime();
			// 如果剩餘時間小於0,時間不合法,說明線程未獲取到鎖
			if (nanosTimeout <= 0L)
				return false;
			// 剩餘時間>0時處理
			// shouldParkAfterFailedAcquire 判斷當前節點是否可以掛起,
			// nanosTimeout > spinForTimeoutThreshold 表示如果線程剩餘時間大於1000納秒,則線程直接掛起nanosTimeout納秒
			if (shouldParkAfterFailedAcquire(p, node) &&
				nanosTimeout > spinForTimeoutThreshold)
				LockSupport.parkNanos(this, nanosTimeout);
			// 如果線程已經中斷,則線程掛起
			if (Thread.interrupted())
				throw new InterruptedException();
		}
	} finally {
		// 如果自旋完成後,已經沒有獲取到鎖,則將當前節點waitStatus置爲失效
		if (failed)
			cancelAcquire(node);
	}
}

            * addWaiter(Node mode):添加當前節點到AQS同步隊列,該方法是一個常用底層方法,後續不再分析!這部分也是線程在AQS同步隊列中的存在方式,mode會被封裝爲Node節點的nextWaiter屬性,標識當前屬性的共享還是獨佔

private Node addWaiter(Node mode) {
	// 包裝當前線程爲Node對象,mode爲傳遞的Node.EXCLUSIVE,表示獨佔鎖
    // Node.EXCLUSIVE節點被包裝爲Node節點的nextWaiter屬性,用於後續判斷
	Node node = new Node(Thread.currentThread(), mode);
	// 將tail節點標識爲前置節點
	Node pred = tail;
	// 判斷前置節點(即tail節點)是否爲空
	// 不爲空表示同步隊列已經創建
	if (pred != null) {
		// 設置當前節點的前置節點爲剛纔賦值的尾結點
		node.prev = pred;
		// 通過CAS將tail節點替換爲node節點
		if (compareAndSetTail(pred, node)) {
			// 將前置節點(即原tail節點)的next節點設置爲當前節點
			// 此處注意順序問題,即先向前指向,再向後指向
			// 所以在遍歷時統一向上遍歷,防止向下遍歷時,存在併發導致只創建了前指,還沒有創建後指時遍歷不到
			pred.next = node;
			return node;
		}
	}
	// 如果爲空,則表示同步隊列還沒有創建,則進行創建
	enq(node);
	return node;
}

            * enq(final Node node):添加節點時,如果同步隊列爲空,則進行創建

private Node enq(final Node node) {
	// 自旋進行創建
	for (;;) {
		// 繼續獲取尾結點
		Node t = tail;
		// 尾結點爲空時,
		// 此處如果直接不爲空說明存在併發
		if (t == null) {
			// 創建一個空節點表示頭節點,並將頭節點指向尾結點
			// 如果創建失敗,說明存在併發已經創建了頭節點,繼續自旋走非空處理
			// 如果創建成功,此時同步隊列中只有初始化的這一個節點,此時頭節點和尾結點相同
			if (compareAndSetHead(new Node()))
				tail = head;
		// 尾結點不爲空時
		} else {
			// 將尾結點設置爲當前節點的上一個節點
			node.prev = t;
			// CAS設置當前節點爲新的尾結點
			if (compareAndSetTail(t, node)) {
				// 設置原尾結點的下一個節點爲當前節點
				// 此處繼續重視雙向設置的前後問題
				t.next = node;
				// 最終返回上一個節點,此處在部分業務類中有應用
				return t;
			}
		}
	}
}

            * setHead(Node node):獲取鎖成功後,重新修改頭節點,並將頭節點置空

private void setHead(Node node) {
	// 設置當前節點爲頭結點
	head = node;
	// 設置當前節點的thread爲空,頭結點只表示爲一個空節點
	node.thread = null;
	// 設置當前節點的前置節點爲空
	node.prev = null;
}

            * shouldParkAfterFailedAcquire(Node pred, Node node):判斷線程在競爭鎖失敗後是否可以掛起;線程掛起後,waitStatus爲-1

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	// 獲取前置節點 waitStatus
	int ws = pred.waitStatus;
	// 前置節點 waitStatus 表示還在等待中,則直接掛起當前線程
	if (ws == Node.SIGNAL)
		return true;
	// 前置節點 waitStatus > 0,說明 waitStatus 的狀態爲失效狀態(CANCELLED = 1)
	if (ws > 0) {
		// 遍歷清除所有失效的前置狀態,直到找到上一個不失效的前置節點,標位當前節點前置
		do {
			node.prev = pred = pred.prev;
		} while (pred.waitStatus > 0);
		// 並把遍歷到的前置節點的next節點指向當前節點
		pred.next = node;
	} else {
		// 前置節點未失效,設置前置節點狀態爲等待,
		compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	}
	// 前置節點爲其他狀態,基於前置節點可以快速執行完,則繼續自旋等待
	return false;
}

            * cancelAcquire(Node node):自旋完成後已經沒有獲取到鎖,或線程自旋過程中被中斷

private void cancelAcquire(Node node) {
	if (node == null)
		return;

	node.thread = null;

	// 自Node節點向上循環,提出已經失效節點
	// 循環到最近的一個非失效節點停止
	Node pred = node.prev;
	while (pred.waitStatus > 0)
		node.prev = pred = pred.prev;

	// 獲取重新賦值的前置節點的下一個節點
	Node predNext = pred.next;

	// 將當前節點的節點狀態 waitStatus 置爲失效
	node.waitStatus = Node.CANCELLED;

	// 如果當前節點爲尾結點,則設置尾結點爲剛纔遍歷到的前置節點
	// 並將前置節點的next節點置爲null
	if (node == tail && compareAndSetTail(node, pred)) {
		compareAndSetNext(pred, predNext, null);
	// 當前節點爲中間節點
	} else {
		int ws;
		// 此處一串判斷,主要爲了判斷前置節點是否頭節點
		// 如果前置節點不是頭節點,且在等待中,則將前置節點的 next 節點設置爲當前節點的 next 節點
		// 當前節點被掛空移除
		if (pred != head &&
			((ws = pred.waitStatus) == Node.SIGNAL ||
			 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
			pred.thread != null) {
			Node next = node.next;
			if (next != null && next.waitStatus <= 0)
				compareAndSetNext(pred, predNext, next);
		} else {
			// 前置節點爲頭結點,則可能已經執行完釋放鎖並喚醒下一個線程
			// 但是由於當前操作已經被鏈表進行了重新構造,則直接喚醒一個線程執行
			// 下一個線程被喚醒後,會再次嘗試獲取鎖,獲取到則執行,獲取不到則繼續掛起
			unparkSuccessor(node);
		}

		node.next = node; // help GC
	}
}

            * unparkSuccessor(Node node):喚醒下一個有效線程

private void unparkSuccessor(Node node) {
	// 獲取當前節點的 waitStatus
	int ws = node.waitStatus;
	// 如果當前節點狀態還在等待中,則修改爲0,表示線程執行中
	if (ws < 0)
		compareAndSetWaitStatus(node, ws, 0);

	// 獲取當前線程的下一個節點,表示將要喚醒的節點
	Node s = node.next;
	// 如果下一個節點爲空,或者節點失效
	if (s == null || s.waitStatus > 0) {
		s = null;
		// 如果鏈表存在斷層,從尾結點開始向上找,找到有效節點賦值給需要喚醒的節點
		// 向上一直找到當前節點,則表示鏈表遍歷了一遍,找到的有效節點即爲node的下一個有效節點
		for (Node t = tail; t != null && t != node; t = t.prev)
			if (t.waitStatus <= 0)
				s = t;
	}
	// 有效喚醒節點不爲空,則喚醒當前節點所在的線程
	if (s != null)
		LockSupport.unpark(s.thread);
}

    3,加鎖及加鎖失敗後的線程park

        * acquire(int arg)

public final void acquire(int arg) {
	// tryAcquire(arg):首先嚐試獲取鎖
	// addWaiter(Node.EXCLUSIVE):嘗試獲取鎖失敗,封裝當前線程爲Node對象,並添加到AQS同步隊列尾部,注意此處傳遞參數爲獨佔鎖,之前已經分析
	// acquireQueued:線程掛起以及線程再次鎖競爭
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

        * acquireQueued(final Node node, int arg):獨佔鎖被喚醒後,不會嘗試去喚醒下一個節點線程

final boolean acquireQueued(final Node node, int arg) {
	boolean failed = true;
	try {
		boolean interrupted = false;
        // 此處自旋,線程掛起後會阻塞
        // 等線程被喚醒,則繼續循環嘗試獲取鎖,獲取鎖失敗,繼續當前流程
        // 獲取鎖成功,則繼續處理線程
		for (;;) {
			// 獲取當前節點的前置節點
			final Node p = node.predecessor();
			// 前置節點爲head節點時,再次嘗試獲取鎖
			if (p == head && tryAcquire(arg)) {
				// 獲取鎖成功後,將當前節點置爲頭節點
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return interrupted;
			}
			// shouldParkAfterFailedAcquire:獲取鎖失敗後,判斷線程是否可以掛起,之前已經分析
			// parkAndCheckInterrupt:線程掛起,並判斷線程是否已經中斷,如果中斷,設置中斷標識爲true
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		// 發生異常後,設置當前節點爲失效,之前已經分析
		if (failed)
			cancelAcquire(node);
	}
}

        * parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
	// 當前線程掛起
	LockSupport.park(this);
	// 返回當前線程中斷狀態,注意:此處調用後,線程中斷狀態爲復位,即無論當前狀態是什麼,返回結果後,會自動改爲false
	return Thread.interrupted();
}

        * 線程掛起後,會等待頭節點線程釋放後,進行線程喚醒

    4,嘗試釋放鎖

        * tryRelease(int arg):該方法爲實現類方法, 在具體實現類中再具體分析

protected boolean tryRelease(int arg) {
	throw new UnsupportedOperationException();
}

    5,釋放鎖及釋放後下一個節點喚醒

        * release(int arg):釋放當前線程鎖狀態

public final boolean release(int arg) {
	// 首先嚐試釋放鎖,釋放鎖成功
	if (tryRelease(arg)) {
		// 獲取頭節點,判斷頭節點狀態
		// 存在等待節點的waitStatus肯定<0
		Node h = head;
		if (h != null && h.waitStatus != 0)
			// 喚醒下一個等待節點
			unparkSuccessor(h);
		return true;
	}
	return false;
}

    6,喚醒後的線程繼續執行加鎖流程

        * acquireQueued(final Node node, int arg):線程喚醒後,繼續自旋獲取鎖,獲取鎖成功後,隔離頭節點,並將當前節點設置爲頭節點!接第三步

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)) {
				// 當節點節點設置爲頭節點,並將頭節點掛空,等待GC回收
                // 此處注意不會喚醒下一個節點線程
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return interrupted;
			}
			// 初次自旋,當前線程掛起
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

四,AQS共享鎖源碼分析

    1,從一段共享鎖代碼開始

package com.gupao.concurrent;

import java.util.concurrent.CountDownLatch;

/**
 * 共享鎖代碼演示
 * @author pj_zhang
 * @create 2019-10-01 14:36
 **/
public class ShardTest {

    // 構建countDownLatch,進行全局共享鎖處理
    // 傳遞參數爲線程數量,當數量通過countDown減爲0時,自定喚醒所有共享鎖
    private static final CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) {
        // 循環構建線程數量,與countDownLatch限定數量一致
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(() -> {
                try {
                    // 標識線程已經激動,並直接進行等待,等待countDownLatch喚醒
                    System.out.println(Thread.currentThread().getName()
                            + "進入等待。。。");
                    // 通過countDownLatch等待
                    countDownLatch.await();
                    Thread.sleep(1000);
                    // 線程被喚醒後,打印線程執行時間,此時阻塞線程會基本同時執行
                    // 存在共享鎖線程喚醒及線程調度時間(基本可以忽略)
                    System.out.println(Thread.currentThread().getName()
                            + "執行,時間:" + System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "THREAD_" + i);
            thread.start();
            countDownLatch.countDown();
        }
    }

}

        * 從共享鎖線程演示可以看出,無論是代碼還是線程執行方式明顯與獨佔鎖不一致,那這裏面又會有疑問

        * 基於共享鎖的線程阻塞是如何實現的?

        * 共享鎖的鎖釋放如何實現共享鎖下的所有線程幾乎同時執行(線程調度及線程池阻塞耗時不計算在內)?

    2,嘗試獲取鎖

        2.1,不帶時間獲取鎖

            * tryAcquireShared(int arg):同樣,共享鎖嘗試獲取,基於實現類實現

protected int tryAcquireShared(int arg) {
	throw new UnsupportedOperationException();
}

        2.2,帶時間獲取鎖

            * tryAcquireSharedNanos(int arg, long nanosTimeout)

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
		throws InterruptedException {
	// 判斷線程是否中斷,中斷直接拋異常
	if (Thread.interrupted())
		throw new InterruptedException();
	// tryAcquireShared:嘗試獲取共享鎖,此處依舊通過實現類實現
	// doAcquireSharedNanos:嘗試釋放共享鎖,在指定納秒內
	return tryAcquireShared(arg) >= 0 ||
		doAcquireSharedNanos(arg, nanosTimeout);
}

            * doAcquireSharedNanos(int arg, long nanosTimeout)

private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
		throws InterruptedException {
	if (nanosTimeout <= 0L)
		return false;
	final long deadline = System.nanoTime() + nanosTimeout;
	// 包裝當前線程爲Node節點,注意此處傳遞參數爲Node.SHARED,表示共享鎖
	final Node node = addWaiter(Node.SHARED);
	boolean failed = true;
	try {
		// 自旋獲取鎖
		for (;;) {
			// 獲取上一個節點
			final Node p = node.predecessor();
			// 前置節點爲頭節點
			if (p == head) {
				// 嘗試獲取共享鎖
				int r = tryAcquireShared(arg);
				// 共享鎖獲取成功
				if (r >= 0) {
					// 設置當前節點爲頭節點,並遞歸喚醒後續共享節點,此部分後續分析
					setHeadAndPropagate(node, r);
					p.next = null; // help GC
					failed = false;
					return true;
				}
			}
			// 後續定時與獨佔鎖一致
			nanosTimeout = deadline - System.nanoTime();
			if (nanosTimeout <= 0L)
				return false;
			if (shouldParkAfterFailedAcquire(p, node) &&
				nanosTimeout > spinForTimeoutThreshold)
				LockSupport.parkNanos(this, nanosTimeout);
			if (Thread.interrupted())
				throw new InterruptedException();
		}
	} finally {
		if (failed)
			// 異常後,節點失效
			cancelAcquire(node);
	}
}

    3,加鎖及加鎖失敗後的線程park

        * acquireShared(int arg):獲取共享鎖

public final void acquireShared(int arg) {
	// 嘗試獲取共享鎖,<0說明獲取失敗,該部分在子類中實現
	if (tryAcquireShared(arg) < 0)
		// 進行鎖獲取處理
		doAcquireShared(arg);
}

        * doAcquireShared(int arg):包裝Node節點,並添加到AQS同步隊列後線程掛起,喚醒後繼續嘗試加鎖執行!加鎖成功後,會判斷下一個節點是否共享鎖節點,如果是,會繼續喚醒!

private void doAcquireShared(int arg) {
	// 包裝Node對象,注意此處傳遞mode爲Node.SHARED
	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);
				if (r >= 0) {
					// 鎖獲取成功,設置頭節點,並喚醒下一個爲共享鎖的節點
                    // 此處注意建立鏈式喚醒思想,每一個線程都在park部分被阻塞,每一個線程被喚醒後都會走這一步進行鎖處理,所以每一道都會進行下一個喚醒
                    // 對共享鎖來說,則存在第一個節點喚醒後,會一直鏈式喚醒下去,不會在存在阻塞
					setHeadAndPropagate(node, r);
					p.next = null; // help GC
					if (interrupted)
						selfInterrupt();
					failed = false;
					return;
				}
			}
			// shouldParkAfterFailedAcquire:獲取鎖失敗後,判斷當前線程是否可以掛起
			// parkAndCheckInterrupt:線程掛起
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

        * setHeadAndPropagate(Node node, int propagate):設置頭節點並喚醒下一個共享鎖節點

private void setHeadAndPropagate(Node node, int propagate) {
	// 獲取頭節點,並將當前節點設置爲頭節點
	Node h = head;
	setHead(node);
	// 條件判斷爲有效節點
	if (propagate > 0 || h == null || h.waitStatus < 0 ||
		(h = head) == null || h.waitStatus < 0) {
		Node s = node.next;
		// 判斷下一個節點是否爲共享鎖,如果是共享鎖,則直接喚醒
		if (s == null || s.isShared())
                        // 喚醒節點,喚醒部分詳解
			doReleaseShared();
	}
}

    4,嘗試釋放鎖

        * tryReleaseShared(int arg):該方法依舊在子類中實現

protected boolean tryReleaseShared(int arg) {
	throw new UnsupportedOperationException();
}

    5,釋放鎖及釋放後下一個節點喚醒

        * releaseShared(int arg):釋放共享鎖

public final boolean releaseShared(int arg) {
	// 嘗試釋放共享鎖
	if (tryReleaseShared(arg)) {
		// 共享鎖釋放
		doReleaseShared();
		return true;
	}
	return false;
}

        * doReleaseShared():喚醒下一個共享鎖節點

private void doReleaseShared() {
	// 自旋進行喚醒
	for (;;) {
		Node h = head;
		if (h != null && h != tail) {
			int ws = h.waitStatus;
			if (ws == Node.SIGNAL) {
				if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
					continue;            // loop to recheck cases
				// 直接喚醒下一個節點,
				unparkSuccessor(h);
			}
			else if (ws == 0 &&
					 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
				continue;                // loop on failed CAS
		}
		if (h == head)                   // loop if head changed
			break;
	}
}

    6,喚醒後的線程繼續執行代碼

        * doAcquireShared(int arg):線程被喚醒後,繼續自旋獲取鎖,獲取鎖成功後進行後續節點處理

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);
				if (r >= 0) {
					// 獲取鎖成功,設置頭節點,並喚醒後續節點
					setHeadAndPropagate(node, r);
					p.next = null; // help GC
					if (interrupted)
						selfInterrupt();
					failed = false;
					return;
				}
			}
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章