ReentrantLock之Condition源碼解讀

1.背景

閱讀該源碼的前提是,已經閱讀了reentrantLock的源碼!

2.await源碼解讀

condition代碼理解的核心,其實就是理解到:

線程節點如何從sync雙向鏈表隊列到指定的條件隊列中,

然後又如何從條件隊列中到sync雙向鏈表隊列的

一定要先把下面的2個圖理解到,再去看源碼和斷點調試就很容易理解了

核心邏輯圖:

 核心代碼邏輯圖:

 

2.1.await方法詳解

代碼解讀:

  /**
     * 進入條件等待
     */
    public final void await() throws InterruptedException {
        // 是否有中斷標記
        if (Thread.interrupted())
            throw new InterruptedException();
        // 將線程加入到條件等待隊列
        Node node = addConditionWaiter();
       /* fullyRelease(node) 釋放鎖並喚醒後繼節點
         這裏要結合ReentrantLock來理解,執行到這裏說明是獲取到了鎖的,
         這裏就是要釋放ReentrantLock鎖,然後進入到條件隊列中等待*/
        int savedState = fullyRelease(node);
        // interruptMode =0表示沒有中斷, interruptMode =1表示退出時重新中斷 ,interruptMode=-1表示退出時拋異常
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
            // 在條件隊列中等待
            LockSupport.park(this);
            // checkInterruptWhileWaiting(node)返回0,表示沒有中斷
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        // acquireQueued(node, savedState) 從sync隊列中重新獲取鎖,並處理中斷標記
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        // node結點不是最後一個結點,清除條件隊列中無效的節點
        if (node.nextWaiter != null) // clean up if cancelled
            unlinkCancelledWaiters();
        // 重新處理中斷,具體中斷方式取決於 interruptMode 的值,
        // interruptMode =1表示退出時重新中斷 ,interruptMode=-1表示退出時拋異常int interruptMode = 0;
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }

 

2.2.addConditionWaiter方法詳解

代碼解讀:

  /**
     * 添加新的節點到條件隊列
     * 這裏的條件隊列是 單鏈表,不是雙鏈表
     * CONDITION = -2 表示是條件隊列
     */
    private Node addConditionWaiter() {
        Node t = lastWaiter;
        // t.waitStatus != Node.CONDITION ?? 這個條件的作用
        if (t != null && t.waitStatus != Node.CONDITION) {
            // 去掉取消節點
            unlinkCancelledWaiters();
            // 將取消的節點,去掉後,尾節點可能會變
            t = lastWaiter;
        }
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        if (t == null)
            // 第一次進入條件隊列
            firstWaiter = node;
        else
            // 將當前節點放在尾節點之後
            t.nextWaiter = node;
        // 設置新的尾節點
        lastWaiter = node;
        // 返回當前節點
        return node;
    }

 

2.3.fullyRelease方法詳解

代碼解讀:

 /**
     * 釋放鎖,並喚醒後繼節點
     *
     * @param node
     * @return
     */
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            // 獲取當前資源狀態
            int savedState = getState();
            // release(savedState) 釋放鎖並喚醒後繼節點
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            // 釋放鎖失敗節點取消
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

 

2.4.isOnSyncQueue方法詳解

代碼解讀:

 /**
     * 判定節點是否在sync隊列中
     *
     * @param node
     * @return
     */
    final boolean isOnSyncQueue(Node node) {
        // 如果節點標記位是CONDITION = -2的狀態 或者 沒有前繼節點,說明節點不在隊列中
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        // 如果下一個節點不爲空說明節點在隊列裏面
        if (node.next != null) // If has successor, it must be on queue
            return true;
        // 遍歷sync隊列,查看節點是否在隊列裏面
        return findNodeFromTail(node);
    }

 

2.5.findNodeFromTail方法詳解

代碼解讀:

   /**
     * 遍歷節點,查看傳入的節點是否在隊列裏面,在裏面返回true
     *
     * @param node
     * @return
     */
    private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (; ; ) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            // 從後往前遍歷,還記得之前我們在講ReentrantLock的源碼時說過爲什麼要從後往前遍歷麼?
            t = t.prev;
        }
    }

 

2.6.checkInterruptWhileWaiting方法詳解

代碼解讀:

 /**
     * // 該模式意味着在退出等待時重新中斷
     * private static final int REINTERRUPT = 1;
     * // 該模式意味着在退出等待時拋出InterruptedException
     * private static final int THROW_IE = -1;
     *
     * @param node
     * @return
     */
    private int checkInterruptWhileWaiting(Node node) {
        // 當前線程沒有被中斷直接返回0
        // 當前線程已經被中斷了的話
        return Thread.interrupted() ?
                // 取消時重新入隊列成功,標記爲退出時拋出異常
                // 取消時重新入隊列失敗,標記位退出時重新中斷
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
    }

 

2.7.transferAfterCancelledWait方法詳解

代碼解讀:

 /**
     * 條件等待隊列中的節點如果已經被取消,將節點添加到sync隊列的尾部
     *
     * @param node
     * @return 節點添加到尾部成功返回true, 否則返回false
     */
    final boolean transferAfterCancelledWait(Node node) {
        // 初始化節點
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            // 將節點添加到尾部
            enq(node);
            // 添加到隊列成功返回true
            return true;
        }
        // 判斷節點是否在Sync隊列裏面
        while (!isOnSyncQueue(node))
            // 不在隊列裏面則等待,直到線程執行完成
            Thread.yield();
        // 添加失敗,返回false
        return false;
    }

 

2.8.unlinkCancelledWaiters方法詳解

代碼解讀:

 /**
     * 作用:刪除單項向鏈表中已經取消的節點,即狀態不等於2的節點
     * 這是典型單向鏈表刪除節點的邏輯,如果對這個段代碼不是很理解,
     * 可以查看之前的數據結果部分
     */
    private void unlinkCancelledWaiters() {
        Node t = firstWaiter;
        Node trail = null;
        while (t != null) {
            Node next = t.nextWaiter;
            if (t.waitStatus != Node.CONDITION) {
                // 斷開連接,幫助GC回收
                t.nextWaiter = null;
                if (trail == null)
                    // 重新定義頭結點
                    firstWaiter = next;
                else
                    trail.nextWaiter = next;
                if (next == null)
                    // 最後的有效尾節點
                    lastWaiter = trail;
            } else {
                trail = t;
            }
            // 指針後移
            t = next;
        }
    }

 

2.9.reportInterruptAfterWait方法詳解

代碼解讀:

 /**
     * 中斷的具體處理方式
     * interruptMode =1表示退出時重新中斷 ,interruptMode=-1表示退出時拋異常
     *
     * @param interruptMode
     * @throws InterruptedException
     */
    private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
        if (interruptMode == THROW_IE)
            // 拋出異常的處理方式
            throw new InterruptedException();
        else if (interruptMode == REINTERRUPT)
            // 自我中斷的處理方式
            selfInterrupt();
    }

 

2.10.selfInterrupt方法詳解

代碼解讀:

 /**
     * 當前線程執行中斷處理
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

 

 3.signal源碼解讀

3.1.signal源碼解讀

代碼:

    /**
     * 喚醒條件隊列中的節點
     */
    public final void signal() {
        // 檢查當前線程是否是擁有鎖的線程
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            // 執行喚醒方法
            doSignal(first);
    }

 


3.2.isHeldExclusively源碼解讀

代碼:

    /**
     * 判定當前線程是否是擁有鎖的線程
     * @return
     */
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

 


3.3.doSignal源碼解讀

代碼:

    /**
     * 執行喚醒條件隊列中的節點
     * @param first
     */
    private void doSignal(Node first) {
        do {
            // 這個if的判定就是檢查條件隊列中是否還有節點,如果沒有了,就將lastWaiter設置爲null
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            // 第一個節點出隊列後,斷開引用
            first.nextWaiter = null;
        } while (!transferForSignal(first) && (first = firstWaiter) != null);
    }

 


3.4.transferForSignal源碼解讀

 /**
     * 喚醒條件隊列中的節點--> 到 sync對列中去
     * @param node
     * @return
     */
    final boolean transferForSignal(Node node) {
        // 修改狀態爲 sync隊列的初始化狀態
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        // 將節點加入到隊列尾部
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            // 換醒節點
            LockSupport.unpark(node.thread);
        return true;
    }

 

4.測試

不論你是否理解了源碼,都建議大家多使用斷點調試查看

節點是如何進入隊列,

如何出隊列,

如何掛起線程,

如何喚醒線程的.....

測試代碼

package com.my.aqs.condition;

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

public class ConditionTest {
    // 功能標記位
    private int flag = 1;
    // 創建非公平鎖
    private Lock lock = new ReentrantLock(false);
    // 條件鎖1-燒水
    private Condition condition1 = lock.newCondition();
    // 條件鎖2-泡茶
    private Condition condition2 = lock.newCondition();
    // 條件鎖3-喝茶
    private Condition condition3 = lock.newCondition();

    /**
     * 功能:燒水
     */
    public void method01() {
        String threadName = Thread.currentThread().getName();
        try {
            lock.lock();
            while (flag != 1) {
                System.out.println("           "+threadName + ",需要,掛起當前線程,進入條件等待隊列,因爲當前不是燒水標記1,而是:" + flag);
                condition1.await();
            }
            System.out.println(threadName + ":正在燒水...");
           // System.out.println(threadName + ":燒水完成,喚醒泡茶線程");
            flag = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 功能:泡茶
     */
    public void method02() {
        String threadName = Thread.currentThread().getName();
        try {
            lock.lock();
            while (flag != 2) {
                System.out.println("           "+threadName + ",需要,掛起當前線程,進入條件等待隊列,因爲當前不是泡茶標記2,而是:" + flag);
                condition2.await();
            }
            System.out.println(threadName + ":正在泡茶...");
           // System.out.println(threadName + ":泡茶完成,喚醒喝茶線程");
            flag = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 功能:喝茶
     */
    public void method03() {
        String threadName = Thread.currentThread().getName();
        try {
            lock.lock();
            while (flag != 3) {
                System.out.println("           "+threadName + ",需要,掛起當前線程,進入條件等待隊列,因爲當前不是喝茶標記3,而是:" + flag);
                condition3.await();
            }
            System.out.println(threadName + ":正在喝茶...");
           // System.out.println(threadName + ":喝茶完成,喚醒燒水線程");
            flag = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 睡眠時間是爲了讓線程按照這個順序進入隊列等待 喝茶->泡茶->燒水
     * @param args
     */
    public static void main(String[] args) {
        ConditionTest conditionTest = new ConditionTest();
        for (int i = 1; i <= 2; i++) {
            // 燒水
            new Thread(() -> {
                try {
                    Thread.sleep(5*1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                conditionTest.method01();
            }, "燒水-線程 " + i).start();
            // 泡茶
            new Thread(() -> {
                try {
                    Thread.sleep(5*100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                conditionTest.method02();
            }, "泡茶-線程 " + i).start();
            // 喝茶
            new Thread(() -> {
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                conditionTest.method03();
            }, "喝茶-線程 " + i).start();
        }
    }
}
View Code

 斷點調試圖:

 測試結果:

完美

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