主頁 > 前端設計 > 資深大廠JAVA架構師帶你剖析Condition原始碼

資深大廠JAVA架構師帶你剖析Condition原始碼

2020-09-16 02:42:30 前端設計

點關注,不迷路!如果本文對你有幫助的話不要忘記點贊支持哦!

1. Condition 定義

Condition是JUC里面提供于控制執行緒釋放鎖, 然后進行等待其他獲取鎖的執行緒發送 signal 信號來進行喚醒的工具類.
主要特點:

  1. Condition內部主要是由一個裝載執行緒節點 Node 的 Condition Queue 實作
  2. 對 Condition 的方法(await, signal等) 的呼叫必需是在本執行緒獲取了獨占鎖的前提下
  3. 因為 操作Condition的方法的前提是獲取獨占鎖, 所以 Condition Queue 內部是一條不支持并發安全的單向 queue (這是相對于 AQS 里面的 Sync Queue)

先看一下一個常見的 demo

import org.apache.log4j.Logger;

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

/**
 * 此demo用于測驗 condition
 * Created by xujiankang on 2017/2/8.
 */
public class ConditionTest {

    private static final Logger logger = Logger.getLogger(ConditionTest.class);

    static final Lock lock = new ReentrantLock();
    static final Condition condition = lock.newCondition();

    public static void main(String[] args) throws Exception{

        final Thread thread1 = new Thread("Thread 1 "){
            @Override
            public void run() {
                lock.lock(); // 執行緒 1獲取 lock
                logger.info(Thread.currentThread().getName() + " 正在運行 .....");

                try {
                    Thread.sleep(2 * 1000);
                    logger.info(Thread.currentThread().getName() + " 停止運行, 等待一個 signal ");
                    condition.await(); // 呼叫 condition.await 進行釋放鎖, 將當前節點封裝成一個 Node 放入 Condition Queue 里面, 等待喚醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logger.info(Thread.currentThread().getName() + " 獲取一個 signal, 繼續執行 ");
                lock.unlock(); // 釋放鎖
            }
        };

        thread1.start();  // 執行緒 1 線運行

        Thread.sleep(1 * 1000);

        Thread thread2 = new Thread("Thread 2 "){
            @Override
            public void run() {
                lock.lock();        // 執行緒 2獲取lock
                logger.info(Thread.currentThread().getName() + " 正在運行.....");
                thread1.interrupt(); // 對執行緒1 進行中斷 看看中斷后會怎么樣? 結果 執行緒 1還是獲取lock, 并且最后還進行 lock.unlock()操作

                try {
                    Thread.sleep(2 * 1000);
                }catch (Exception e){

                }
                condition.signal(); // 發送喚醒信號 從 AQS 的 Condition Queue 里面轉移 Node 到 Sync Queue
                logger.info(Thread.currentThread().getName() + " 發送一個 signal ");
                logger.info(Thread.currentThread().getName() + " 發送 signal 結束");
                lock.unlock(); // 執行緒 2 釋放鎖
            }
        };

        thread2.start();

    }


}

整個執行步驟

  1. 執行緒 1 開始執行, 獲取 lock, 然后開始睡眠 2秒
  2. 當執行緒1睡眠到 1秒時, 執行緒2開始執行, 但是lock被執行緒1獲取, 所以 等待
  3. 執行緒 1 睡足2秒 呼叫 condition.await() 進行鎖的釋放, 并且將 執行緒1封裝成一個 node 放到 condition 的 Condition Queue里面, 等待其他獲取鎖的執行緒給他 signal, 或對其進行中斷(中斷后可以到 Sync Queue里面進而獲取 鎖)
  4. 執行緒 2 獲取鎖成功, 中斷 執行緒1, 執行緒被中斷后, node 從 Condition Queue 轉移到 Sync Queue 里面, 但是 lock 還是被 執行緒2獲取者, 所以 node呆在 Sync Queue 里面等待獲取 lock
  5. 執行緒 2睡了 2秒, 開始 用signal喚醒 Condition Queue 里面的節點(此時代表 執行緒1的node已經到 Sync Queue 里面)
  6. 執行緒 2釋放lock, 并且在 Sync Queue 里面進行喚醒等待獲取鎖的節點 node
  7. 執行緒1 得到喚醒, 獲取鎖
  8. 執行緒1 釋放鎖

執行結果

[2017-02-08 22:43:09,557] INFO  Thread 1  (ConditionTest.java:26) - Thread 1  正在運行 .....
[2017-02-08 22:43:11,565] INFO  Thread 1  (ConditionTest.java:30) - Thread 1  停止運行, 等待一個 signal 
[2017-02-08 22:43:11,565] INFO  Thread 2  (ConditionTest.java:48) - Thread 2  正在運行.....
java.lang.InterruptedException
[2017-02-08 22:43:13,566] INFO  Thread 2  (ConditionTest.java:57) - Thread 2  發送一個 signal 
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
[2017-02-08 22:43:13,566] INFO  Thread 2  (ConditionTest.java:58) - Thread 2  發送 signal 結束
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
[2017-02-08 22:43:13,567] INFO  Thread 1  (ConditionTest.java:35) - Thread 1  獲取一個 signal, 繼續執行 
    at com.lami.tuomatuo.search.base.concurrent.aqs.ConditionTest$1.run(ConditionTest.java:31)

2. Condition 建構式級基本屬性

主要是Condition Queue 的頭尾節點(這里頭尾節點不需要進行初始化)

/** First node of condition queue */
/** Condition Queue 里面的頭節點 */
private transient Node firstWaiter;
/** Last node of condition queue */
/** Condition Queue 里面的尾節點 */
private transient Node lastWaiter;

/** Creates a new {@code ConditionObject} instance */
/** 建構式 */
public ConditionObject(){}

3. Condition Queue enqueue節點方法 addConditionWaiter

addConditionWaiter方法主要用于呼叫 Condition.await 時將當前節點封裝成 一個Node, 加入到 Condition Queue里面

大家可以注意下, 下面對 Condition Queue 的操作都沒考慮到 并發(Sync Queue 的佇列是支持并發操作的), 這是為什么呢? 因為在進行操作 Condition 是當前的執行緒已經獲取了AQS的獨占鎖, 所以不需要考慮并發的情況

/**
 * Adds a new waiter to wait queue
 * 將當前執行緒封裝成一個 Node 節點 放入大 Condition Queue 里面
 * 大家可以注意到, 下面對 Condition Queue 的操作都沒考慮到 并發(Sync Queue 的佇列是支持并發操作的), 這是為什么呢? 因為在進行操作 Condition 是當前的執行緒已經獲取了AQS的獨占鎖, 所以不需要考慮并發的情況
 * @return
 */
private Node addConditionWaiter(){
    Node t = lastWaiter;                                // 1. Condition queue 的尾節點
    // If lastWaiter is cancelled, clean out              // 2.尾節點已經Cancel, 直接進行清除,
                                                          //    這里有1個問題, 1 何時出現t.waitStatus != Node.CONDITION -> 在對執行緒進行中斷時 ConditionObject -> await -> checkInterruptWhileWaiting -> transferAfterCancelledWait "compareAndSetWaitStatus(node, Node.CONDITION, 0)" <- 導致這種情況一般是 執行緒中斷或 await 超時
                                                          //    一個注意點: 當Condition進行 awiat 超時或被中斷時, Condition里面的節點是沒有被洗掉掉的, 需要其他 await 在將執行緒加入 Condition Queue 時呼叫addConditionWaiter而進而洗掉, 或 await 操作差不多結束時, 呼叫 "node.nextWaiter != null" 進行判斷而洗掉 (PS: 通過 signal 進行喚醒時 node.nextWaiter 會被置空, 而中斷和超時時不會)
    if(t != null && t.waitStatus != Node.CONDITION){
        unlinkCancelledWaiters();                        // 3. 呼叫 unlinkCancelledWaiters 對 "waitStatus != Node.CONDITION" 的節點進行洗掉(在Condition里面的Node的waitStatus 要么是CONDITION(正常), 要么就是 0 (signal/timeout/interrupt))
        t = lastWaiter;                                // 4. 獲取最新的 lastWaiter
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION); // 5. 將執行緒封裝成 node 準備放入 Condition Queue 里面
    if(t == null){
        firstWaiter = node;                           // 6 .Condition Queue 是空的
    }else{
        t.nextWaiter = node;                          // 7. 最加到 queue 尾部
    }
    lastWaiter = node;                                // 8. 重新賦值 lastWaiter
    return node;
}

4. Condition 喚醒 first節點方法 doSignal

這里的喚醒指的是將節點從 Condition Queue 轉移到 Sync Queue 里面

/**
 * Removes and transfers nodes until hit non-cancelled one or
 * null. Split out from signal in part to encourage compilers
 * to inline the case of no waiters
 * @param first
 */
/**
 * 喚醒 Condition Queue 里面的頭節點, 注意這里的喚醒只是將 Node 從 Condition Queue 轉到 Sync Queue 里面(這時的 Node 也還是能被 Interrupt)
 */
private void doSignal(Node first){
    do{
        if((firstWaiter = first.nextWaiter) == null){ // 1. 將 first.nextWaiter 賦值給 nextWaiter 為下次做準備
            lastWaiter = null;                          // 2. 這時若 nextWaiter == null, 則說明 Condition 為空了, 所以直接置空 lastWaiter
        }
        first.nextWaiter = null;                        // 3.  first.nextWaiter == null 是判斷 Node 從 Condition queue 轉移到 Sync Queue 里面是通過 signal 還是 timeout/interrupt
    }while(!transferForSignal(first) && (first = firstWaiter) != null); // 4. 呼叫  transferForSignal將 first 轉移到 Sync Queue 里面, 回傳不成功的話, 將 firstWaiter 賦值給 first
}

5. Condition 喚醒 所有 節點方法 doSignalAll

/**
 * Removes and transfers all nodes
 * @param first (non-null) the first node on condition queue
 */
/**
 * 喚醒 Condition Queue 里面的所有的節點
 */
private void doSignalAll(Node first){
    lastWaiter = firstWaiter = null;       // 1. 將 lastWaiter, firstWaiter 置空
    do{
        Node next = first.nextWaiter;        // 2. 初始化下個換新的節點
        first.nextWaiter = null;            // 3.  first.nextWaiter == null 是判斷 Node 從 Condition queue 轉移到 Sync Queue 里面是通過 signal 還是 timeout/interrupt
        transferForSignal(first);             // 4. 呼叫  transferForSignal將 first 轉移到 Sync Queue 里面
        first = next;                         // 5. 開始換新 next 節點
    }while(first != null);
}

6. Condition 洗掉取消節點的方法 unlinkCancelledWaiters

一般的節點都會被 signal 喚醒, 從 Condition Queue 轉移到 Sync Queue, 而若遇到 interrupt 或 等待超時, 則直接改變 node 的狀態(從 CONDITION 變成 0), 并直接放入 Sync 里面, 而不清理Condition Queue 里面的節點, 所以需要下面的函式

/**
 * http://czj4451.iteye.com/blog/1483264
 *
 * Unlinks cancelled waiter nodes from condition queue
 * Called only while holding lock. This is called when
 * cancellation occured during condition wait, and upon
 * insertion of a new waiter when lastWaiter is seen to have
 * been cancelled. This method is needed to avoid garbage
 * retention in the absence of signals. So even though it may
 * require a full traversal, it comes intot play when
 * timeouts or cancellations all nodes rather than stoppping at a
 * particular target to unlink all pointers to garbege nodes
 * without requiring many re-traversals during cancellation
 * storms
 */
/**
 * 在 呼叫 addConditionWaiter 將執行緒放入 Condition Queue 里面時 或 awiat 方法獲取 差不多結束時 進行清理 Condition queue 里面的因 timeout/interrupt 而還存在的節點
 * 這個洗掉操作比較巧妙, 其中引入了 trail 節點, 可以理解為traverse整個 Condition Queue 時遇到的最后一個有效的節點
 */
private void unlinkCancelledWaiters(){
    Node t = firstWaiter;
    Node trail = null;
    while(t != null){
        Node next = t.nextWaiter;               // 1. 先初始化 next 節點
        if(t.waitStatus != Node.CONDITION){   // 2. 節點不有效, 在Condition Queue 里面 Node.waitStatus 只有可能是 CONDITION 或是 0(timeout/interrupt引起的)
            t.nextWaiter = null;               // 3. Node.nextWaiter 置空
            if(trail == null){                  // 4. 一次都沒有遇到有效的節點
                firstWaiter = next;            // 5. 將 next 賦值給 firstWaiter(此時 next 可能也是無效的, 這只是一個臨時處理)
            }else{
                trail.nextWaiter = next;       // 6. next 賦值給 trail.nextWaiter, 這一步其實就是洗掉節點 t
            }
            if(next == null){                  // 7. next == null 說明 已經 traverse 完了 Condition Queue
                lastWaiter = trail;
            }
        }else{
            trail = t;                         // 8. 將有效節點賦值給 trail
        }
        t = next;
    }
}

毫無疑問, 這是一段非常精巧的queue節點洗掉, 主要還是在 節點 trail 上, trail 節點可以理解為traverse整個 Condition Queue 時遇到的最后一個有效的節點

7. Condition 喚醒首節點方法 signal

/**
 * Moves the longest-waiting thread, if one exists, from the
 * wait queue for this condition to the wait queue for the
 * owning lock
 *
 * @throws IllegalMonitorStateException if{@link #isHeldExclusively()}
 *          returns {@code false}
 */
/**
 * 將 Condition queue 的頭節點轉移到 Sync Queue 里面
 * 在進行呼叫 signal 時, 當前的執行緒必須獲取了 獨占的鎖
 */
@Override
public void signal() {
    if(!isHeldExclusively()){       // 1. 判斷當前的執行緒是否已經獲取 獨占鎖
        throw new IllegalMonitorStateException();
    }
    Node first = firstWaiter;
    if(first != null){
        doSignal(first);           // 2. 呼叫 doSignal 進行轉移
    }
}

上述面試題答案都整理成檔案筆記, 也還整理了一些面試資料&最新2020收集的一些大廠的面試真題(都整理成檔案,小部分截圖),有需要的可以 點擊進入暗號:csdn ,

8. Condition 喚醒所有節點方法 signalAll

/**
 * Moves all threads from the wait queue for this condition to
 * the wait queue for the owning lock
 *
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively()}
 *          return {@code false}
 */
/**
 * 將 Condition Queue 里面的節點都轉移到 Sync Queue 里面
 */
public final void signalAll(){
    if(!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    Node first = firstWaiter;
    if(first != null){
        doSignalAll(first);
    }
}

9. Condition 釋放鎖進行等待方法 awaitUninterruptibly

awaitUninterruptibly 方法是一個不回應 中斷的方法
整個流程

  1. 將當前的執行緒封裝成 Node 加入到 Condition 里面
  2. 丟到當前執行緒所擁有的 獨占鎖
  3. 等待 其他獲取 獨占鎖的執行緒的喚醒, 喚醒從 Condition Queue 到 Sync Queue 里面, 進而獲取 獨占鎖
  4. 最后獲取 lock 之后, 在根據執行緒喚醒的方式(signal/interrupt) 進行處理

/**
 * Implements uninterruptible condition wait
 * <li>
 *     Save lock state return by {@link #getState()}
 * </li>
 *
 * <li>
 *     Invoke {@link #release(int)} with saved state as argument,
 *     throwing IllegalMonitoringStateException if it fails
 *     Block until signalled
 *     Reacquire by invoking specified version of
 *     {@link #acquire(int)} with saved state as argument
 * </li>
 */
/**
 * 不回應執行緒中斷的方式進行 await
 */
public final void awaitUninterruptibly(){
    Node node = addConditionWaiter();       // 1. 將當前執行緒封裝成一個 Node 放入 Condition Queue 里面
    int savedState = fullyRelease(node);   // 2. 釋放當前執行緒所獲取的所有的獨占鎖(PS: 獨占的鎖支持重入), 等等, 為什么要釋放呢? 以為你呼叫 awaitUninterruptibly 方法的前提就是你已經獲取了 獨占鎖
    boolean interrupted = false;         // 3. 執行緒中斷標識
    while(!isOnSyncQueue(node)){          // 4. 這里是一個 while loop, 呼叫 isOnSyncQueue 判斷當前的 Node 是否已經被轉移到 Sync Queue 里面
       LockSupport.park(this);            // 5. 若當前 node 不在 sync queue 里面, 則先 block 一下等待其他執行緒呼叫 signal 進行喚醒; (這里若有其他執行緒對當前執行緒進行 中斷的換, 也能進行喚醒)
        if(Thread.interrupted()){         // 6. 判斷這是喚醒是 signal 還是 interrupted(Thread.interrupted()會清楚執行緒的中斷標記, 但沒事, 我們有步驟7中的interrupted進行記錄)
            interrupted = true;           // 7. 說明這次喚醒是被中斷而喚醒的,這個標記若是true的話, 在 awiat 離開時還要 自己中斷一下(selfInterrupt), 其他的函式可能需要執行緒的中斷標識
        }
    }
    if(acquireQueued(node, savedState) || interrupted){ // 8. acquireQueued 回傳 true 說明執行緒在 block 的程序中式被 inetrrupt 過(其實 acquireQueued 回傳 true 也有可能其中有一次喚醒是 通過 signal)
        selfInterrupt();                 // 9. 自我中斷, 外面的執行緒可以通過這個標識知道, 整個 awaitUninterruptibly 運行程序中 是否被中斷過
    }
}

10. Condition 中斷標示

下面兩個是用于追蹤 呼叫 awaitXXX 方法時執行緒有沒有被中斷過
主要的區別是
1. REINTERRUPT: 代表執行緒是在 signal 后被中斷的 (REINTERRUPT = re-interrupt 再次中斷 最后會呼叫 selfInterrupt)
2. THROW_IE: 代表在接受 signal 前被中斷的, 則直接拋出例外 (Throw_IE = throw inner exception)

/**
 * For interruptible waits, we need to track whether to throw
 * InterruptedException, if interrupted while blocked on
 * condition, versus reinterrupt current thread, if
 * interrupted while blocked waiting to re-acquire
 */
/**
 * 下面兩個是用于追蹤 呼叫 awaitXXX 方法時執行緒有沒有被中斷過
 * 主要的區別是
 *      REINTERRUPT: 代表執行緒是在 signal 后被中斷的        (REINTERRUPT = re-interrupt 再次中斷 最后會呼叫 selfInterrupt)
 *      THROW_IE: 代表在接受 signal 前被中斷的, 則直接拋出例外 (Throw_IE = throw inner exception)
 */
/** Mode meaning to reinterrupt on exit from wait */
/** 在離開 awaitXX方法, 退出前再次 自我中斷 (呼叫 selfInterrupt)*/
private static final int REINTERRUPT = 1;
/** Mode meaning to throw InterruptedException on exit from wait */
/**  在離開 awaitXX方法, 退出前再次, 以為在 接受 signal 前被中斷, 所以需要拋出例外 */
private static final int THROW_IE = -1;

11. Condition 中斷處理方法

該方法主要是查 在 awaitXX 方法中的這次喚醒是否是中斷引起的, 若是中斷引起的, 就進行 Node 的轉移

/**
 * Checks for interrupt, returning THROW_IE if interrupted
 * before signalled, REINTERRUPT if after signalled, or
 * 0 if not interrupted
 */
/**
 * 檢查 在 awaitXX 方法中的這次喚醒是否是中斷引起的
 * 若是中斷引起的, 則將 Node 從 Condition Queue 轉移到 Sync Queue 里面
 * 回傳值的區別:
 *      0: 此次喚醒是通過 signal -> LockSupport.unpark
 *      THROW_IE: 此次的喚醒是通過 interrupt, 并且 在 接受 signal 之前
 *      REINTERRUPT: 執行緒的喚醒是 接受過 signal 而后又被中斷
 */
private int checkInterruptWhileWaiting(Node node){
    return Thread.interrupted() ?
            (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}

12. Condition 中斷處理方法 reportInterruptAfterWait

/**
 * Throws InterruptedException, reinterrupts current thread, or
 * does nothing, depending on mode
 */
/**
 * 這個方法是在 awaitXX 方法離開前呼叫的, 主要是根據
 * interrupMode 判斷是拋出例外, 還是自我再中斷一下
 */
private void reportInterruptAfterWait(int interrupMode) throws InterruptedException{
    if(interrupMode == THROW_IE){
        throw new InterruptedException();
    }
    else if(interrupMode == REINTERRUPT){
        selfInterrupt();
    }
}

13. Condition 釋放鎖 進行等待的方法 await

await 此方法回應中斷請求, 當接受到中斷請求后會將節點從 Condition Queue 轉移到 Sync Queue

/**
 * Implements interruptible condition wait
 *
 * <li>
 *     If current thread is interrupted, throw InterruptedException
 *     Save lock state returned by {@link #getState()}
 *     Invoke {@link #release(int)} with saved state as argument,
 *     throwing IllegalMonitorStateException if it fails
 *     Blocking until signalled or interrupted
 *     Reacquire by invoking specifized version of
 *     {@link #acquire(int)} with saved state as argument.
 *     If interrupted while blocked in step 4, throw InterruptedException
 * </li>
 *
 * @throws InterruptedException
 */
/**
 * 支持 InterruptedException 的 await <- 注意這里即使是執行緒被中斷,
 * 還是需要獲取了獨占的鎖后, 再 呼叫 lock.unlock 進行釋放鎖
 */
@Override
public final void await() throws InterruptedException {
    if(Thread.interrupted()){                       // 1. 判斷執行緒是否中斷
        throw new InterruptedException();
    }
    Node node = addConditionWaiter();               // 2. 將執行緒封裝成一個 Node 放到 Condition Queue 里面, 其中可能有些清理作業
    int savedState = fullyRelease(node);           // 3. 釋放當前執行緒所獲取的所有的鎖 (PS: 呼叫 await 方法時, 當前執行緒是必須已經獲取了獨占的鎖)
    int interruptMode = 0;
    while(!isOnSyncQueue(node)){                  // 4. 判斷當前執行緒是否在 Sync Queue 里面(這里 Node 從 Condtion Queue 里面轉移到 Sync Queue 里面有兩種可能 (1) 其他執行緒呼叫 signal 進行轉移 (2) 當前執行緒被中斷而進行Node的轉移(就在checkInterruptWhileWaiting里面進行轉移))
        LockSupport.park(this);                   // 5. 當前執行緒沒在 Sync Queue 里面, 則進行 block
        if((interruptMode = checkInterruptWhileWaiting(node)) != 0){    // 6. 判斷此次執行緒的喚醒是否因為執行緒被中斷, 若是被中斷, 則會在checkInterruptWhileWaiting的transferAfterCancelledWait 進行節點的轉移; 回傳值 interruptMode != 0
            break;                                                      // 說明此是通過執行緒中斷的方式進行喚醒, 并且已經進行了 node 的轉移, 轉移到 Sync Queue 里面
        }
    }
    if(acquireQueued(node, savedState) && interruptMode != THROW_IE){ // 7. 呼叫 acquireQueued在 Sync Queue 里面進行 獨占鎖的獲取, 回傳值表明在獲取的程序中有沒有被中斷過
        interruptMode = REINTERRUPT;
    }
    if(node.nextWaiter != null){ // clean up if cancelled       // 8. 通過 "node.nextWaiter != null" 判斷 執行緒的喚醒是中斷還是 signal, 因為通過中斷喚醒的話, 此刻代表執行緒的 Node 在 Condition Queue 與 Sync Queue 里面都會存在
        unlinkCancelledWaiters();                                  // 9. 進行 cancelled 節點的清除
    }
    if(interruptMode != 0){                                     // 10. "interruptMode != 0" 代表通過中斷的方式喚醒執行緒
        reportInterruptAfterWait(interruptMode);                // 11. 根據 interruptMode 的型別決定是拋出例外, 還是自己再中斷一下
    }
}

14. Condition 釋放鎖 進行等待的方法 awaitNanos

awaitNanos 具有超時功能, 與回應中斷的功能, 不管中斷還是超時都會 將節點從 Condition Queue 轉移到 Sync Queue

/**
 * Impelemnts timed condition wait
 *
 * <li>
 *     If current thread is interrupted, throw InterruptedException
 *     Save lock state returned by {@link #getState()}
 *     Invoke {@link #release(int)} with saved state as argument,
 *     throwing IllegalMonitorStateException if it fails
 *     Block until aignalled, interrupted, or timed out
 *     Reacquire by invoking specified version of
 *     {@link #acquire(int)} with saved state as argument
 *     If interrupted while blocked in step 4, throw InterruptedException
 * </li>
 */
/**
 * 所有 awaitXX 方法其實就是
 *  0. 將當前的執行緒封裝成 Node 加入到 Condition 里面
 *  1. 丟到當前執行緒所擁有的 獨占鎖,
 *  2. 等待 其他獲取 獨占鎖的執行緒的喚醒, 喚醒從 Condition Queue 到 Sync Queue 里面, 進而獲取 獨占鎖
 *  3. 最后獲取 lock 之后, 在根據執行緒喚醒的方式(signal/interrupt) 進行處理
 *  4. 最后還是需要呼叫 lock./unlock 進行釋放鎖
 */
@Override
public final long awaitNanos(long nanosTimeout) throws InterruptedException {
    if(Thread.interrupted()){                                   // 1. 判斷執行緒是否中斷
        throw new InterruptedException();
    }
    Node node = addConditionWaiter();                           // 2. 將執行緒封裝成一個 Node 放到 Condition Queue 里面, 其中可能有些清理作業
    int savedState = fullyRelease(node);                       // 3. 釋放當前執行緒所獲取的所有的鎖 (PS: 呼叫 await 方法時, 當前執行緒是必須已經獲取了獨占的鎖)
    final long deadline = System.nanoTime() + nanosTimeout;   // 4. 計算 wait 的截止時間
    int interruptMode = 0;
    while(!isOnSyncQueue(node)){                              // 5. 判斷當前執行緒是否在 Sync Queue 里面(這里 Node 從 Condtion Queue 里面轉移到 Sync Queue 里面有兩種可能 (1) 其他執行緒呼叫 signal 進行轉移 (2) 當前執行緒被中斷而進行Node的轉移(就在checkInterruptWhileWaiting里面進行轉移))
        if(nanosTimeout <= 0L){                               // 6. 等待時間超時(這里的 nanosTimeout 是有可能 < 0),
            transferAfterCancelledWait(node);                 //  7. 呼叫 transferAfterCancelledWait 將 Node 從 Condition 轉移到 Sync Queue 里面
            break;
        }
        if(nanosTimeout >= spinForTimeoutThreshold){      // 8. 當剩余時間 < spinForTimeoutThreshold, 其實函式 spin 比用 LockSupport.parkNanos 更高效
            LockSupport.parkNanos(this, nanosTimeout);       // 9. 進行執行緒的 block
        }
        if((interruptMode = checkInterruptWhileWaiting(node)) != 0){   // 10. 判斷此次執行緒的喚醒是否因為執行緒被中斷, 若是被中斷, 則會在checkInterruptWhileWaiting的transferAfterCancelledWait 進行節點的轉移; 回傳值 interruptMode != 0
            break;                                                     // 說明此是通過執行緒中斷的方式進行喚醒, 并且已經進行了 node 的轉移, 轉移到 Sync Queue 里面
        }
        nanosTimeout = deadline - System.nanoTime();                    // 11. 計算剩余時間
    }

    if(acquireQueued(node, savedState) && interruptMode != THROW_IE){ // 12. 呼叫 acquireQueued在 Sync Queue 里面進行 獨占鎖的獲取, 回傳值表明在獲取的程序中有沒有被中斷過
        interruptMode = REINTERRUPT;
    }
    if(node.nextWaiter != null){                                    // 13. 通過 "node.nextWaiter != null" 判斷 執行緒的喚醒是中斷還是 signal, 因為通過中斷喚醒的話, 此刻代表執行緒的 Node 在 Condition Queue 與 Sync Queue 里面都會存在
        unlinkCancelledWaiters();                                      // 14. 進行 cancelled 節點的清除
    }
    if(interruptMode != 0){                                           // 15. "interruptMode != 0" 代表通過中斷的方式喚醒執行緒
        reportInterruptAfterWait(interruptMode);                      // 16. 根據 interruptMode 的型別決定是拋出例外, 還是自己再中斷一下
    }
    return deadline - System.nanoTime();                            // 17 這個回傳值代表是 通過 signal 還是 超時
}

15. Condition 釋放鎖 進行等待的方法 awaitUntil

/**
 * Implements absolute timed condition wait
 * <li>
 *     If current thread is interrupted, throw InterruptedException
 *     Save lock state returned by {@link #getState()}
 *     Invoke {@link #release(int)} with saved state as argument,
 *     throwing IllegalMonitorStateException if it fails
 *     Block until signalled, interrupted, or timed out
 *     Reacquire by invoking specialized version of
 *     {@link #acquire(int)} with saved state as argument
 *     if interrupted while blocked in step 4, throw InterruptedException
 *     If timeed out while blocked in step 4, return false, else true
 * </li>
 */
/**
 * 所有 awaitXX 方法其實就是
 *  0. 將當前的執行緒封裝成 Node 加入到 Condition 里面
 *  1. 丟到當前執行緒所擁有的 獨占鎖,
 *  2. 等待 其他獲取 獨占鎖的執行緒的喚醒, 喚醒從 Condition Queue 到 Sync Queue 里面, 進而獲取 獨占鎖
 *  3. 最后獲取 lock 之后, 在根據執行緒喚醒的方式(signal/interrupt) 進行處理
 *  4. 最后還是需要呼叫 lock./unlock 進行釋放鎖
 *
 *  awaitUntil 和 awaitNanos 差不多
 */
@Override
public boolean awaitUntil(Date deadline) throws InterruptedException {
    long abstime = deadline.getTime();                                      // 1. 判斷執行緒是否中斷
    if(Thread.interrupted()){
        throw new InterruptedException();
    }
    Node node = addConditionWaiter();                                       // 2. 將執行緒封裝成一個 Node 放到 Condition Queue 里面, 其中可能有些清理作業
    int savedState = fullyRelease(node);                                   // 3. 釋放當前執行緒所獲取的所有的鎖 (PS: 呼叫 await 方法時, 當前執行緒是必須已經獲取了獨占的鎖)
    boolean timeout = false;
    int interruptMode = 0;
    while(!isOnSyncQueue(node)){                                           // 4. 判斷當前執行緒是否在 Sync Queue 里面(這里 Node 從 Condtion Queue 里面轉移到 Sync Queue 里面有兩種可能 (1) 其他執行緒呼叫 signal 進行轉移 (2) 當前執行緒被中斷而進行Node的轉移(就在checkInterruptWhileWaiting里面進行轉移))
        if(System.currentTimeMillis() > abstime){                          // 5. 計算是否超時
            timeout = transferAfterCancelledWait(node);                    //  6. 呼叫 transferAfterCancelledWait 將 Node 從 Condition 轉移到 Sync Queue 里面
            break;
        }
        LockSupport.parkUntil(this, abstime);                              // 7. 進行 執行緒的阻塞
        if((interruptMode = checkInterruptWhileWaiting(node)) != 0){       // 8. 判斷此次執行緒的喚醒是否因為執行緒被中斷, 若是被中斷, 則會在checkInterruptWhileWaiting的transferAfterCancelledWait 進行節點的轉移; 回傳值 interruptMode != 0
            break;                                                         // 說明此是通過執行緒中斷的方式進行喚醒, 并且已經進行了 node 的轉移, 轉移到 Sync Queue 里面
        }
    }

    if(acquireQueued(node, savedState) && interruptMode != THROW_IE){   // 9. 呼叫 acquireQueued在 Sync Queue 里面進行 獨占鎖的獲取, 回傳值表明在獲取的程序中有沒有被中斷過
        interruptMode = REINTERRUPT;
    }
    if(node.nextWaiter != null){                                       // 10. 通過 "node.nextWaiter != null" 判斷 執行緒的喚醒是中斷還是 signal, 因為通過中斷喚醒的話, 此刻代表執行緒的 Node 在 Condition Queue 與 Sync Queue 里面都會存在
        unlinkCancelledWaiters();                                         // 11. 進行 cancelled 節點的清除
    }
    if(interruptMode != 0){                                             // 12. "interruptMode != 0" 代表通過中斷的方式喚醒執行緒
        reportInterruptAfterWait(interruptMode);                        // 13. 根據 interruptMode 的型別決定是拋出例外, 還是自己再中斷一下
    }

    return !timeout;                                                   // 13. 回傳是否通過 interrupt 進行執行緒的喚醒
}

16. Condition 的 instrumentation 方法

/**
 * Returns true if this condition was created by the given
 * synchronization object
 */
/**判斷當前 condition 是否獲取 獨占鎖 */
final boolean isOwnedBy(KAbstractOwnableSynchronizer sync){
    return sync == KAbstractQueuedSynchronizer.this;
}

/**
 * Quires whether any threads are waiting on this condition
 * Implements {@link KAbstractOwnableSynchronizer#"hasWaiters(ConditionObject)}
 *
 * @return {@code true} if there are any waiting threads
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively()}
 *          returns {@code false}
 */
/**
 * 查看 Condition Queue 里面是否有 CONDITION 的節點
 */
protected final boolean hasWaiters(){
    if(!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    for(Node w = firstWaiter; w != null; w = w.nextWaiter ){
        if(w.waitStatus == Node.CONDITION){
            return true;
        }
    }
    return false;
}

/**
 * Returns an estimate of the number of threads waiting on
 * this condition
 * Implements {@link KAbstractOwnableSynchronizer#"getWaitQueueLength()}
 *
 * @return the estimated number of waiting threads
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively()}
 *          return {@code false}
 */
/**
 * 獲取 Condition queue 里面的  CONDITION 的節點的個數
 */
protected final int getWaitQueueLength(){
    if(!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    int n = 0;
    for(Node w = firstWaiter; w != null; w = w.nextWaiter){
        if(w.waitStatus == Node.CONDITION){
            ++n;
        }
    }
    return n;
}

/**
 * Returns a collection containing those threads that may be
 * waiting on this Condition
 * Implements {@link KAbstractOwnableSynchronizer#'getWaitingThreads}
 *
 * @return the collection of thread
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively()}
 *          returns {@code false}
 */
/**
 * 獲取 等待的執行緒
 */
protected final Collection<Thread> getWaitingThreads(){
    if(!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    ArrayList<Thread> list = new ArrayList<>();
    for(Node w = firstWaiter; w != null; w = w.nextWaiter){
        if(w.waitStatus == Node.CONDITION){
            Thread t = w.thread;
            if(t != null){
                list.add(t);
            }
        }
    }
    return list;
}

到此這篇關于文章就結束了!

點關注,不迷路!如果本文對你有幫助的話不要忘記點贊支持哦!

上述面試題答案都整理成檔案筆記, 也還整理了一些面試資料&最新2020收集的一些大廠的面試真題(都整理成文檔,小部分截圖),有需要的可以 點擊進入暗號:csdn ,

希望對大家有所幫助,有用的話點贊給我支持!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/52174.html

標籤:其他

上一篇:[TopOpt] 針對99行改進的88行拓撲優化程式完全注釋

下一篇:Jeecg Boot 2.3 里程碑版本發布,支持微服務和單體自由切換、提供新行編輯表格JVXETable

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more