【線程】ReentrantLock + Condition 源碼剖析 (九)

我的原則:先會用再說,內部慢慢來


一、await 與 signal 實例

public class _12_01_TestCondition{
	public static void main(String[] args) throws Exception{
		LockDemo lockDemo = new LockDemo();
		new Thread(() -> {
			try {
				lockDemo.await();
			} catch (Exception e) {
				e.printStackTrace();
			}
		},"A").start();

		new Thread(() -> {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			lockDemo.signal();

		},"B").start();
	}
	static class LockDemo{
		ReentrantLock lock = new ReentrantLock();
		Condition condition = lock.newCondition();

		public void await() throws Exception{
			System.out.println(Thread.currentThread().getName() + ",condition ready to lock ");
			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to await ");
			condition.await();
			System.out.println(Thread.currentThread().getName() + ",condition ready to unlock ");
			lock.unlock();
		}
		public void signal(){
			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to signal ");
			condition.signal();
			lock.unlock();
		}
	}
}
  • 輸出:
A,condition ready to lock 
A,condition ready to await 
B,condition ready to signal 
A,condition ready to unlock 
  • 結論

A 線程首先條件 lock , B 線程去喚醒, A線程繼續往下跑

二、await 與 signal 實例,小小改動帶來的Bug思考

把 lock 和 unlock 註釋掉。

public void signal(){
//			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to signal ");
			condition.signal();
//			lock.unlock();
		}

  • 輸出:
A,condition ready to lock 
A,condition ready to await 
B,condition ready to signal 
Exception in thread "B" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
	at indi.sword.util.concurrent._12_01_TestCondition$LockDemo.signal(_12_01_TestCondition.java:52)
	at indi.sword.util.concurrent._12_01_TestCondition.lambda$main$1(_12_01_TestCondition.java:33)
	at java.lang.Thread.run(Thread.java:748)
  • 結論

拋出了 IllegalMonitorStateException 異常:
IllegalMonitorStateException 講解:【線程】 Object.wait 內部原理(二)

爲什麼會這樣呢?
原因就是: condition.await(); 方法,已經釋放鎖了,接下來看 condition.await(); 方法。

三、 lock.newCondition()

  1. await()
public interface Condition {
    void await() throws InterruptedException;
}

由此可見,Condition 是一個 interface,await 需要被實現。

  1. 接下來看下初始化方法 Condition condition = lock.newCondition() ;
	// java.util.concurrent.locks.ReentrantLock#newCondition
    public Condition newCondition() {
        return sync.newCondition();
    }
	// java.util.concurrent.locks.ReentrantLock.Sync#newCondition
	 final ConditionObject newCondition() {
       return new ConditionObject();
	}

注意上面的 newCondition() 方法是 final,所以Sync 的實現類 NonfailSync 與 FailSync 統一使用該方法。

四、condition.await() 源碼剖析

  • 接下來重點看一下 ConditionObject 的 await 方法
void ConditionObject#await(){ 
	// thread 是否被打斷,
    if (Thread.interrupted())
        throw new InterruptedException();
    // 1. 加入到 ConditionWaiter 裏面去    
    Node node = addConditionWaiter();
    // 2. 釋放掉當前Node中的thread所佔用的lock
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 3. 判斷是否在同步隊列中,是的話就阻塞。因爲喚醒後,會從同步隊列中被移除。
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        // 已經被喚醒了,看下線程是否被打斷,打斷的話,就break
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 4. 被喚醒後,將節點從 Condition 隊列移動到同步Sync隊列,內部的 trylock方法將會獲得成功,跑完下面的代碼
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
    	// 如果node有下一個waiter,那麼取消掉那些 cancel狀態的 
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
  1. addConditionWaiter 方法
public final void AbstractQueuedSynchronizer.ConditionObject#addConditionWaiter{
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
    	// 移除掉waitStatus不是 CONDITION 的Node
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 創建一個 CONDITION 狀態的 Node
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

1.1 unlinkCancelledWaiters 方法

// 移除掉waitStatus不是 CONDITION 的Node
private void AbstractQueuedSynchronizer.ConditionObject#unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            // 只有第一次循環,且第一個的waitStatus不是Node.CONDITION的時候,纔會進去下面的if
            if (trail == null)
                firstWaiter = next;
            else
            	// 鏈表跳過中間的 t
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}
  1. fullyRelease 方法 (釋放掉當前Node中的thread所佔用的lock)
final int AbstractQueuedSynchronizer#fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        // 釋放鎖 
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
        	// 若當前沒有持有鎖,那麼拋 IllegalMonitorStateException 異常
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

2.1 release 方法

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

後續 release方法的深入,直接看上一篇文章有講解這個,這裏不累贅
【線程】ReentrantLock 源碼剖析 (八)

  1. isOnSyncQueue 方法
// 判斷是否在同步Sync隊列中,是的話,就阻塞
final boolean AbstractQueuedSynchronizer#isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    return findNodeFromTail(node);
}

3.1 findNodeFromTail 方法

private boolean AbstractQueuedSynchronizer#findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}
  1. acquireQueued 方法
// node 入同步隊列
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)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

五、 單 Await 場景剖析

  1. await 之前,lock.lock 獲取鎖

在這裏插入圖片描述

  1. addConditionWaiter :加入到 ConditionWaiter 裏面去
    在這裏插入圖片描述

  2. fullyRelease :釋放掉當前Node中的thread所佔用的lock
    在這裏插入圖片描述

  3. LockSupport.park(this); 進入阻塞,等待喚醒

六、單 condition.signal() 源碼剖析

  • 看下 signal 方法
public final void AbstractQueuedSynchronizer.ConditionObject#signal() {
	// 若當前thread 沒有持有鎖,那麼拋出Exception
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
    	// 喚醒第一個 waiter
        doSignal(first);
}
  • 看下 doSignal 方法
private void AbstractQueuedSynchronizer.ConditionObject#doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

在這裏插入圖片描述

  • 看下 transferForSignal 方法

 // transferForSignal把當前節點從condition隊列遷移到同步隊列中,並設置waitStatus爲SIGNAL
final boolean AbstractQueuedSynchronizer#transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 如果不能更改waitStatus,則該Node已被取消
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    // 前驅節點,enq 是把節點加到 Sync的尾巴端
    Node p = enq(node);
    int ws = p.waitStatus;

    // TODO: debug 的時候,不知道 p.waitStatus 爲什麼會突然從 0 變成 -1. 納悶。
     // 正常不是在這裏喚醒的,是在lock.unclock的時候,release的時候喚醒的
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
    	// 如果是 cancel 狀態或者是其他狀態,纔會進入這個 unpark 方法,提前叫醒。
        LockSupport.unpark(node.thread);
    return true;
}

方法本意是將一個節點從Condition隊列轉換爲AbstractQueuedSynchronizer隊列,
總結一下方法的實現:

  1. 嘗試將Node的waitStatus從CONDITION置爲0,這一步失敗直接返回false
  2. 當前節點進入調用enq方法進入AbstractQueuedSynchronizer隊列
  3. 當前節點通過CAS機制將waitStatus置爲SIGNAL
    最後上面的步驟全部成功,返回true,返回true喚醒等待節點成功。從喚醒的代碼我們可以得出一個重要結論:某個await()的節點被喚醒之後並不意味着它後面的代碼會立即執行,它會被加入到AbstractQueuedSynchronizer隊列的尾部,只有前面等待的節點獲取鎖全部完畢才能輪到它。

在這裏插入圖片描述

  • 某個await()的節點被喚醒之後並不意味着它後面的代碼會立即執行,它會被加入到AbstractQueuedSynchronizer隊列的尾部,只有前面等待的節點獲取鎖全部完畢才能輪到它。也就是 unlock 的時候,會喚醒同步隊列的Node內部的 thread。

=========== 至此,單Condition,單Await,單Signal流程結束 ===========

七、多個 condition.await() 實例demo

public class _12_02_TestCondition {
	public static void main(String[] args) throws Exception{
		LockDemo lockDemo = new LockDemo();
		for (int i = 0; i < 5; i++) {
			new Thread(() -> {
				try {
					lockDemo.await();
				} catch (Exception e) {
					e.printStackTrace();
				}
			},"A" + i).start();
		}
		// 確保上面代碼先執行
		Thread.sleep(1000);
		Scanner sc = new Scanner(System.in);
		System.out.println("點擊任意鍵喚醒線程 ...");
		sc.nextLine();

		for (int i = 0; i < 5; i++) {
			new Thread(() -> {
				lockDemo.signal();

			},"B" + i).start();
		}
	}
	static class LockDemo{
		ReentrantLock lock = new ReentrantLock();
		Condition condition = lock.newCondition();
		public void await() throws Exception{
			System.out.println(Thread.currentThread().getName() + ",condition ready to lock ");
			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to await ");
			condition.await();
			System.out.println(Thread.currentThread().getName() + ",condition ready to unlock ");
			lock.unlock();
		}
		public void signal(){
			System.out.println(Thread.currentThread().getName() + ",condition ready to lock ");
			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to signal ");
			condition.signal();
			System.out.println(Thread.currentThread().getName() + ",condition ready to unlock ");
			lock.unlock();
		}
	}
}

輸出:

A0,condition ready to lock 
A0,condition ready to await 
A1,condition ready to lock 
A1,condition ready to await 
A2,condition ready to lock 
A2,condition ready to await 
A3,condition ready to lock 
A3,condition ready to await 
A4,condition ready to lock 
A4,condition ready to await 
點擊任意鍵喚醒線程 ...



B0,condition ready to lock 
B0,condition ready to signal 
B0,condition ready to unlock 
A0,condition ready to unlock 
B1,condition ready to lock 
B1,condition ready to signal 
B1,condition ready to unlock 
B3,condition ready to lock 
B3,condition ready to signal 
B3,condition ready to unlock 
B2,condition ready to lock 
B2,condition ready to signal 
B2,condition ready to unlock 
B4,condition ready to lock 
B4,condition ready to signal 
B4,condition ready to unlock 
A1,condition ready to unlock 
A2,condition ready to unlock 
A3,condition ready to unlock 
A4,condition ready to unlock 

七、多個 condition.await() 場景分析

在這裏插入圖片描述

  1. await源碼剖析
  2. addConditionWaiter源碼剖析
  3. addConditionWaiter場景剖析
  • 假設5個線程全部 lock.lock ,都暫時未進入 await 方法。那麼,全部enq 進入同步sync隊列,具體看這個
    【線程】ReentrantLock 源碼剖析 (八)

  • 5個線程全部進入await方法,最終全部線程 park 阻塞,丟到等待隊列,等待再次丟到同步隊列Sync之後被喚醒Signal

一個等待Condition 隊列 + 一個同步Sync隊列:
在這裏插入圖片描述
最終只剩下等待隊列:
在這裏插入圖片描述

八、多個 condition.signal() 場景分析

  1. signal源碼剖析
  2. signal場景剖析
  • signal 的過程,就是把等待隊列裏面的Node添加到Sync隊列的過程:

在這裏插入圖片描述

  • 彩色圖加深理解
    在這裏插入圖片描述

  • 附加一張總體架構圖,同時存在等待隊列和同步Sync隊列:
    在這裏插入圖片描述

  • 跑過 LockSupport.park(this); 之後,已經被Signal,那麼接着往下走,全部丟到 Sync同步隊列裏面。lock.unlock 的時候,將釋放 Sync 隊列中的節點。
    在這裏插入圖片描述
    ======== 全部講解結束 ========

九、番外篇

上一章節:【線程】ReentrantLock 源碼剖析 (八)

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