併發編程(七):AQS之Condition

 

一,底層AQS源碼分析:併發編程(四):AbstractQueuedSynchronizer源碼分析

二,Condition介紹

    1,線程通信

        * 在 synchronized 中,有分析過通過 wait()/notify()/notifyAll() 實現線程通信。同樣,在JUC中,也提供了這樣的工具類,就是Conditon

    2,類圖

        * 從類圖中可以看出,Condition作爲線程通信的頂層類,派生出實現類 ConditionObject,該實現類爲AQS的內部類,並對Node做了擴展

        * 從屬性中可以看出,ConditionObject 中新添加了兩個屬性 firstWaiter lastWaiter,再關聯 Node 中已經定義好的 nextWaiter,組合成爲一個單向鏈表

        * 所以在Condition中,除過繼承自AQS的FIFO雙向鏈表,Condition自身也實現了一個基於Node的單向鏈表。在調用 Condition.await() 時,線程節點被存儲到Condition單向鏈表形成的阻塞隊列中;線程被喚醒後,被從阻塞隊列釋放,並添加到AQS同步隊列中繼續進行鎖爭搶,如下圖

    3,常用API

// 線程等待
void await() throws InterruptedException;
// 線程限時等待,分析其中一個
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 線程喚醒
void signal();
// 線程全部喚醒
void signalAll();

    4,功能DEMO

package com.gupao.concurrent;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Condition測試
 *
 * @author pj_zhang
 * @create 2019-10-08 21:39
 **/
public class ConditionTest {

    private static Lock lock = new ReentrantLock();

    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {

        // 等待線程首先執行,打印第一句話後等待
        new Thread(() -> {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "  ready await...");
            try {
                // 等待線程等待之後,會自動釋放鎖
                condition.await();
                // 等待線程被喚醒後,繼續打印第二句話
                System.out.println(Thread.currentThread().getName() + "  execute done...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 釋放鎖
                lock.unlock();
            }
        }, "THREAD_AWAIT").start();

        // 喚醒線程等待100毫秒,讓等待線程先執行
        new Thread(() -> {
            try {
                Thread.sleep(100);
                // 獲取到等待線程等待釋放的鎖,並打印第一句話
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "  ready signal...");
                // 喚醒等待的線程,此時鎖依舊被喚醒線程持有
                // 等待線程被喚醒後,會自動被添加到等待隊列
                // 如果存在多個等待線程,需要多次signal喚醒
                // signalall()可以喚醒全部等待線程,全部喚醒後在AQS隊列中等待執行
                condition.signal();
                System.out.println(Thread.currentThread().getName() + " execute done...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 執行完成後釋放鎖,由AQS同步隊列繼續爭搶獲取鎖
                lock.unlock();
            }
        }, "THREAD_SIGNAL").start();

    }

}

三,源碼分析

1,等待源碼分析

1.1,不帶時間等待源碼分析

    * await():線程阻塞

public final void await() throws InterruptedException {
	// 判斷線程是否中斷,直接跑移除處理
	if (Thread.interrupted())
		throw new InterruptedException();
	// 添加線程到Condition阻塞隊列隊尾
	Node node = addConditionWaiter();
	// 釋放當前線程獲取到的鎖,並喚醒下一個線程
	// 此處注意重入鎖,一次全釋放
	int savedState = fullyRelease(node);
	int interruptMode = 0;
	// 從尾部開始尋找,判斷當前節點是否已經在AQS同步隊列中存在
	while (!isOnSyncQueue(node)) {
		// 如果存在,直接掛起,此處等待Signal
		LockSupport.park(this);
		// 被喚醒後,獲取中斷狀態,爲0表示沒有被中斷
		if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
			break;
	}
	// 如果不存在,或者掛起被喚醒後,繼續嘗試獲取鎖,
	// 此處已經從Condition阻塞隊列中釋放,在AQS同步隊列中如果沒有直接獲取到鎖,會在AQS同步隊列中排隊等候
	// acquireQueued如果返回true,即已經中斷,會進行&&後面判斷,如果狀態不是THROW_IE,則默認修改爲interruptMode
	if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
		interruptMode = REINTERRUPT;
	if (node.nextWaiter != null)
		// 對失效狀態的節點進行一次清理
		unlinkCancelledWaiters();
	if (interruptMode != 0)
		// 非0表示線程已經被中斷,進行中斷異常處理或者重新置爲中斷狀態
		reportInterruptAfterWait(interruptMode);
}

    * addConditionWaiter():添加線程到Condition阻塞隊列

private Node addConditionWaiter() {
	Node t = lastWaiter;
	// 如果當前節點已經失效,重組Condition阻塞隊列,去除無效節點
	if (t != null && t.waitStatus != Node.CONDITION) {
		unlinkCancelledWaiters();
		// 重新賦值尾結點
		t = lastWaiter;
	}
	// 將當前現成包裝爲Node對象
	Node node = new Node(Thread.currentThread(), Node.CONDITION);
	// 將當前節點添加到隊尾,並跟前置節點建立單向鏈接
	if (t == null)
		firstWaiter = node;
	else
		t.nextWaiter = node;
	lastWaiter = node;
	return node;
}

    * fullyRelease(Node node):釋放當前線程獲得的鎖,注意重入釋放

final int fullyRelease(Node node) {
	boolean failed = true;
	try {
		// 獲取該線程當前的重入次數
		int savedState = getState();
		// 直接對所有重入次數進行釋放,釋放成功後修改標誌位狀態
		if (release(savedState)) {
			failed = false;
			return savedState;
		} else {
			throw new IllegalMonitorStateException();
		}
	} finally {
		// 如果釋放失敗,則節點失效
		if (failed)
			node.waitStatus = Node.CANCELLED;
	}
}
public final boolean release(int arg) {
	// 嘗試釋放arg次鎖
	if (tryRelease(arg)) {
		Node h = head;
		// 釋放成功後,喚醒下一個節點
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);
		return true;
	}
	return false;
}

    * isOnSyncQueue(Node node):判斷當前節點是否已經在AQS同步隊列中存在

final boolean isOnSyncQueue(Node node) {
	// CONDITION狀態的默認不在AQS同步隊列中存在
	if (node.waitStatus == Node.CONDITION || node.prev == null)
		return false;
	if (node.next != null)
		return true;
	// 從根節點開始,進行Node比對
	return findNodeFromTail(node);
}

private boolean findNodeFromTail(Node node) {
	Node t = tail;
	for (;;) {
		if (t == node)
			return true;
		if (t == null)
			return false;
		t = t.prev;
	}
}

    * =======================線程被 signal() 喚醒後,繼續執行如下代碼=========================

    * checkInterruptWhileWaiting(Node node):校驗線程在等待中是否已經被中斷

private int checkInterruptWhileWaiting(Node node) {
	// 線程是否已經中斷 ? (線程是否在等待時中斷?拋異常:重置中斷狀態):沒有中斷返回0
	return Thread.interrupted() ?
		(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
		0;
}

final boolean transferAfterCancelledWait(Node node) {
	// 判斷線程是否在等待的時候被中斷,如果線程被signal()喚醒,狀態則會被修改爲0
    // 此處存在await時間情況,可能會自喚醒
    // 自喚醒如果發現中斷,直接異常處理!signal()喚醒,重置中斷狀態,交由業務代碼處理
	if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
		enq(node);
		return true;
	}
	// 線程被喚醒後中斷,且線程不在AQS隊列中,釋放線程調度時間,直到線程被加到AQS同步隊列中,返回false
	while (!isOnSyncQueue(node))
		Thread.yield();
	return false;
}

    * acquireQueued(final Node node, int arg):嘗試競爭鎖並添加到AQS同步隊列,AQS底層方法,不做分析

    * unlinkCancelledWaiters():清除失效節點

private void unlinkCancelledWaiters() {
	Node t = firstWaiter;
	// 定義一個臨時節點,進行數據傳遞
	Node trail = null;
	while (t != null) {
		Node next = t.nextWaiter;
		// 數據已經失效
		if (t.waitStatus != Node.CONDITION) {
			// 將t的上下指向置空,t節點掛空,等待GC回收
			// 之後重新構建單向鏈表
			t.nextWaiter = null;
			if (trail == null)
				firstWaiter = next;
			else
				trail.nextWaiter = next;
			if (next == null)
				lastWaiter = trail;
		}
		else
			trail = t;
		t = next;
	}
}

    * reportInterruptAfterWait(int interruptMode):線程中斷最終處理,根據前幾步中斷中斷標識進行處理,判斷拋異常還是重置狀態!

private void reportInterruptAfterWait(int interruptMode)
	throws InterruptedException {
	if (interruptMode == THROW_IE)
		throw new InterruptedException();
	else if (interruptMode == REINTERRUPT)
		selfInterrupt();
}

1.2,帶時間等待源碼分析

    * await(long time, TimeUnit unit):帶時間等待,與直接等待差距不大,只是添加了超時喚醒機制

public final boolean await(long time, TimeUnit unit)
		throws InterruptedException {
	long nanosTimeout = unit.toNanos(time);
	if (Thread.interrupted())
		throw new InterruptedException();
	Node node = addConditionWaiter();
	int savedState = fullyRelease(node);
	// 獲取最終超時時間
	final long deadline = System.nanoTime() + nanosTimeout;
	boolean timedout = false;
	int interruptMode = 0;
	while (!isOnSyncQueue(node)) {
		// 剩餘時間小於0,則添加到AQS同步隊列等候
		if (nanosTimeout <= 0L) {
			timedout = transferAfterCancelledWait(node);
			break;
		}
		if (nanosTimeout >= spinForTimeoutThreshold)
			// 線程park指定納秒數,超時後自喚醒執行
			LockSupport.parkNanos(this, nanosTimeout);
		// 線程沒有中斷,跳出循環,進行鎖競爭執行
		if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
			break;
		// 獲取生意時間
		nanosTimeout = deadline - System.nanoTime();
	}
	if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
		interruptMode = REINTERRUPT;
	if (node.nextWaiter != null)
		unlinkCancelledWaiters();
	if (interruptMode != 0)
		reportInterruptAfterWait(interruptMode);
	return !timedout;
}

2,喚醒源碼分析

2.1,喚醒單個線程

    * signal():單個線程喚醒

public final void signal() {
	// 判斷當前線程是否是持有鎖的線程
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	// 獲取Condition阻塞隊列頭節點進行喚醒,排隊連續喚醒
	Node first = firstWaiter;
	if (first != null)
		doSignal(first);
}

    * doSignal(Node first):喚醒節點,注意當前步驟已經涉及了節點移除

private void doSignal(Node first) {
	do {
		// 如果Condition隊列中只有這一個節點,則把尾節點置空
		if ( (firstWaiter = first.nextWaiter) == null)
			lastWaiter = null;
		// 將頭節點的下一個節點置空
		first.nextWaiter = null;
			// 判斷當前節點可以被喚醒,狀態變更失敗,則繼續喚醒下一個節點
			// 狀態變更失敗,可能已經自喚醒
	} while (!transferForSignal(first) &&
			 (first = firstWaiter) != null);
}

    * transferForSignal(Node node):是否轉變喚醒節點

final boolean transferForSignal(Node node) {
	// 狀態變更失敗,線程失效
	if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
		return false;
	// enq返回的是node的前置節點
	Node p = enq(node);
	int ws = p.waitStatus;
	// 前置節點已經失效,喚醒當前線程
	if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
		LockSupport.unpark(node.thread);
	return true;
}

2.2,喚醒全部線程

    * signalAll():喚醒Condition阻塞隊列全部等待線程

public final void signalAll() {
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	Node first = firstWaiter;
	if (first != null)
		// 喚醒全部線程
		doSignalAll(first);
}

    * doSignalAll(Node first):算是對doSignal()條件遍歷的精簡,無條件遍歷整個Condition鏈表,喚醒全部節點

private void doSignalAll(Node first) {
	lastWaiter = firstWaiter = null;
	do {
		Node next = first.nextWaiter;
		first.nextWaiter = null;
		transferForSignal(first);
		first = next;
	// 從第一個節點開始向最後一個節點遍歷,喚醒所有有效的阻塞節點
	} while (first != null);
}

 

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