JUC-AQS-Condition源碼詳解-流程圖很清晰


如果直接看這個有難度,大家可以看下基礎的AQS詳解JUC(一)-AQS源碼分析

一、Condition的作用

二、Condition的數據結構

在這裏插入圖片描述

三、核心源碼解讀

3.1 await()

在這裏插入圖片描述

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            //放棄所有持有的鎖
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //阻塞判斷,當前node是不是在同步隊列時,不在同步隊列那麼就park當前線程
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                //如果被中斷了,則檢測中斷
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //這是此節點已經在同步隊列中了
            //在隊列中獲取鎖,並判斷當前的interruptMode不爲-1,即不是拋出異常
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                //把中斷類型設置爲,重新中斷,意味在線程獲得鎖的時候,重新中斷線程
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

1.先判斷當前線程的中斷狀態,如果爲中斷則拋出異常
2.如果不是中斷狀態,則把當前節點加入Condition隊列中,即我們的等待隊列中
3.釋放當前持有的鎖,並記住當前state的值
4.會阻塞在判斷當前節點是否在同步隊列中,只有當執行了signal(),節點纔會被添加回到同步隊列中,如果不在隊列中,則park當前線程
5.如果在同步隊列中,則嘗試從隊列中獲得鎖
6.獲得鎖之後,需要響應不同的中斷模式

 /**
  * 判斷是否要拋出中斷異常
  */
        private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            //如果中斷模型爲THROW_IE,就拋出異常
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            //如果中斷模型爲REINTERRUPT,就只把線程標記爲中斷
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }
/**
     * Convenience method to interrupt current thread.
     * 中斷當前線程
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

主流程講過了,我們重點看下第二步Node node = addConditionWaiter()
在這裏插入圖片描述
1.獲取當前Condition的lastWaiter
2.判斷lastWaiter的狀態,是否爲Cancelled
3.如果爲Cancelled則處理當前Condition中的節點,清除爲Cancelled的節點,並設置新的lastWaiter
4.如果不爲Cancelled,則用當前線程新建一個狀態爲Condition的Node節點
5.判斷t(lastWaiter)是否爲null,如果爲null,則代表當前等待隊列中已經沒有值了,所以我們把firstWaiter設置爲新建的node
6.如果t不爲null,那我們就可以把t.nextWaiter設置爲node
7.將lastWaiter設置爲node

以上邏輯我們可以通過簡單的類型來劃分一下
在這裏插入圖片描述
上圖是從分類角度來闡述不同場景下的不同處理邏

3.2 signal()

public final void signal() {
            //判斷是否爲當前線程是否爲佔有此鎖的線程
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //獲得到當前的第一個等待節點
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

具體流程
1.先判斷是否爲當前線程是否爲佔有此鎖的線程
2.獲得此等待隊列中第一個等待節點,如果此節點不爲null,則doSignal此節點
下面我們具體看看doSignal()方法的實現
在這裏插入圖片描述
1.先判斷頭結點的下一個節點是否爲null,如果爲null的話,設置lastWaiter爲null
2.如果不爲null的話,因爲頭節點已經獲取出來了,自然要把他的nextWaiter設置爲null
3.根據當前節點是否可以從Condition狀態設置到0(node的初始化狀態),把當前節點轉移到同步隊列中去。
4.判斷在同步隊列中前節點的狀態,判斷是否要unpark()當前線程。

//刪除或者轉移節點到同步隊列中,直到獲取的節點是不可取消節點,或者null
private void doSignal(Node first) {
     do {
     	 //判斷當前節點的下一個節點是否爲null
         if ( (firstWaiter = first.nextWaiter) == null)
              //如果爲null,則證明此等待隊列無數據,把lastWaiter也設置爲null
              lastWaiter = null;
         //first節點需要取出,所以nextWaiter設置爲null     
         first.nextWaiter = null;
        } while (!transferForSignal(first) &&
              (first = firstWaiter) != null);
        }
 //將節點從條件隊列,轉送到同步隊列,如果成功的話,返回true
 final boolean transferForSignal(Node node) {
        /*
         * 如果無法改變waiStatus的值,那麼當前節點已經被取消
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //把節點插入到隊列中,返回node的上一個節點
        Node p = enq(node);
        int ws = p.waitStatus;
        //前節點的狀態>0,即爲Cancelled狀態,或者前節點的狀態設置爲signal失敗
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            //喚醒當前線程
            LockSupport.unpark(node.thread);
        return true;
    }

四、Condition和AQS的關係

Condition是條件隊列,那麼怎麼做到的呢?最終獲取鎖,還是在同步隊列中,那麼如何做到讓執行中的線程,退出擁有的鎖,並等待喚醒呢?java是這麼做的,直接搞一個同步隊列,保存那些等待喚醒的線程。

await()

head執行await,當前head,生成Condition狀態的Node節點,並添加到條件隊列中。在條件隊列中的node是無法獲得到鎖的,所以需要一直檢測,此節點是否被移動到同步隊列中,如果檢測到移動到隊列中,便會嘗試獲得鎖,就可以繼續執行了。

signal()
在這裏插入圖片描述
指定doSignal(firstWaiter)
1.先把當前firstWaiter從條件隊列中完整剝離出來
2.將節點追加到同步隊列的tail後面
3.處理條件節點的隊列,將firstWaiter的指針指向下一個節點
4.判斷同步隊列中的前一個節點的狀態,判斷是否可以unpark()當前線程

五、實戰

源代碼如下,大家可以自行debug,我只取幾個典型的場景,給大家說明一下

public class MyCondition {
    private static Lock lock = new ReentrantLock();
    private static Condition A = lock.newCondition();
    private static Condition B = lock.newCondition();

    private static int count = 0;

    static class ThreadA extends Thread{
        @Override
        public void run() {
            this.setName("ThreadA");
            try {
                lock.lock();
                System.out.println("A加鎖");
                for (int i = 0; i < 10; i++) {

                    while (count%2 != 0){
                    	//第一次斷點
                        System.out.println("A.await()之前");
                        A.await();
                        System.out.println("A等待之後");
                    }
                    System.out.println("A");
                    count++;
                    B.signal();
                    System.out.println("喚醒B");
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    static class ThreadB extends Thread{
        @Override
        public void run() {
            this.setName("ThreadB");
            try {
                lock.lock();
                System.out.println("B加鎖");
                for (int i = 0; i < 10; i++) {
                    while (count%2 != 1){
                        B.await();
                        System.out.println("B等待");
                    }
                    System.out.println("B");
                    //第二次斷點
                    count++;
                    A.signal();
                    //第三次斷點
                    System.out.println("喚醒A");
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) {
        new MyCondition.ThreadA().start();
        new MyCondition.ThreadB().start();
    }
}

1. 在執行A.await()之前的數據

在這裏插入圖片描述
1.當前持有鎖的是ThreadA
2.同步隊列中保存着ThreadB
3.A等待隊列中無數據

2. A執行await()方法之後,在A.signal()之前,我們看下鎖的數據

在這裏插入圖片描述
1.當前持有鎖的是ThreadB
2.同步隊列的tail數據爲空
3.A等待隊列的頭結點爲ThreadA

3.當執行喚醒A之後

在這裏插入圖片描述
1.可以看到當前獲得鎖的線程是ThreadB
2.ThreadA已經被加入到同步隊列中了

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