AQS中的condition是如何實現的

condition的作用

condition的使用場景其實很多,涉及到條件判斷的并發(fā)場景都可以用到,比如:

  • 阻塞隊列的ArrayBlockingQueue中做隊列滿和空的條件判斷
  • CyclicBarrier中做阻塞與喚醒所有線程的判斷
  • DelayQueue中的阻塞獲取隊列數據的判斷
  • 線程池ThreadPoolExecutor中awaitTermination方法的條件判斷

condition怎么用呢?

在使用synchronized時我們可以使用wait()、notify()、notifyAll()方法來調度線程,而condition提供了類似的方法:wait(),signal(),signalAll的功能,并且能夠更加精細的控制等待的范圍,像上面所說,jdk中使用了很多ReentrantLock和condition的配合來實現線程調度

我們看一個conditon最常見的使用方式:生產消費者的模型:

public class ConditionTest {

    LinkedList<String> lists = new LinkedList<>();

    Lock lock = new ReentrantLock();

    //集合是否滿的條件判斷
    Condition fullCondition = lock.newCondition();

    //集合是否空的條件判斷
    Condition emptyCondition = lock.newCondition();

    //生產者
    private void product(){
        lock.lock();
        try {
            //假如集合大小為10
            while (lists.size() == 10){
                System.out.println("list is full");
                fullCondition.await();
            }
            //生產一個5位的隨機字符串
            String randomString = getRandomString(5);
            lists.add(randomString);
            System.out.println(String.format("product %s size %d  %s",randomString,lists.size(),Thread.currentThread().getName()));
            //通知消費者可以消費了
            emptyCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //消費者
    private String consume(){
        lock.lock();
        try{
            while (lists.size() == 0){
                System.out.println("list is empty");
                emptyCondition.await();
            }
            String first = lists.removeFirst();
            //通知生產者可以生產了
            fullCondition.signalAll();
            return first;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return null;
    }
    
    /**
     * 生成隨機字符串
     * @param length
     * @return
     */
    public static String getRandomString(int length){
        String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random=new Random();
        StringBuffer sb=new StringBuffer();
        for(int i=0;i<length;i++){
            int number=random.nextInt(62);
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }

    public static void main(String[] args) {

        ConditionTest test = new ConditionTest();

        ExecutorService executorService = Executors.newCachedThreadPool();

        //線程個數控制消費的快還是生產的快
        for(int i = 0;i<2;i++){

            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName());
                while (true){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.product();
                }
            });
        }

        for(int k = 0;k<1;k++){
            executorService.submit(()->{
                System.out.println("cousumestart");
                while (true) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String consume = test.consume();
                    System.out.println("consume " + consume+ " "+Thread.currentThread().getName() );
                }
            });
        }

        //等待輸入,阻塞主線程不退出
        try {
            new BufferedReader(new InputStreamReader(System.in)).readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
//部分輸出日志
product qeV0r size 7  pool-1-thread-1
product xEUkA size 8  pool-1-thread-2
consume P5Je1 pool-1-thread-3
product rQS1D size 8  pool-1-thread-1
product QcEtf size 9  pool-1-thread-2
consume 2q7Fc pool-1-thread-3
product Z5rBg size 9  pool-1-thread-1
consume UBxBD pool-1-thread-3
product Tr5q2 size 9  pool-1-thread-2
product HXBdE size 10  pool-1-thread-1
list is full
consume aYDNR pool-1-thread-3
product ukjnk size 10  pool-1-thread-2
list is full
consume LBEdA pool-1-thread-3
product iK28H size 10  pool-1-thread-2
list is full
list is full

可以看到生產者線程有2個,消費者線程有1個,生產和消費的速度相同,用Thread.sleep控制,
生產速度大于消費速度,最后集合元素到10個的時候生產者調用fullCondition.await();阻塞,只有消費者消費后通過fullCondition.signalAll();通知生產者繼續(xù)生產

同理添加消費者線程數,使消費的速度快與生產,則集合為空時會調用emptyCondition.await();阻塞,生產者生產后回調用emptyCondition.signalAll();通知消費者繼續(xù)生產

相較于對象的wait()、notifyAll()方法不同的條件分開判斷,顆粒度更小一些,喚醒的線程范圍更精準

再看一下ArrayBlockingQueue的一個例子,在一段時間內阻塞獲取隊列數據,取不到則返回空:

  public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                //notEmpty 是lock new出來的一個condition
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

condition的使用場景還多,下面我們就一起看看condition的實現原理吧,首先condition需要在AbstractQueuedSynchronizer實現類的

condition原理解析

我們知道AQS中維護了一個隊列來控制線程的執(zhí)行,condition中使用了另一個等待隊列來實現條件的判斷,condition必須在aqs的acquire獲取鎖后使用,調用condition.await()方法將添加一個node到條件隊列中,在調用signal()或signalAll()后將此節(jié)點移出condition的等待隊列放到鎖的等待隊列中去競爭鎖,取到鎖后繼續(xù)執(zhí)行后續(xù)邏輯。


condition有以下幾個方法

//將等待時間最長的線程從condition等待隊列放到鎖的等待隊列中
public final void signal()
//將所有等待線程從condition等待隊列放到鎖的等待隊列中
public final void signalAll()
//condition的等待方法
public final void await() throws InterruptedException 
//不可中斷的wait
public final void awaitUninterruptibly()
//幾個有時間參數的wait方法
public final long awaitNanos(long nanosTimeout)
                throws InterruptedException
public final boolean awaitUntil(Date deadline)
                throws InterruptedException
public final boolean await(long time, TimeUnit unit)
                throws InterruptedException               

先看一下最主要的await方法

AbstractQueuedSynchronizer.ConditionObject#await()


        public final void await() throws InterruptedException {
            //如果當前線程被中斷了拋出InterruptedException
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//(1)
            int savedState = fullyRelease(node);//(2)
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//(3)
                //掛起線程
                LockSupport.park(this);
                //中斷情況的判斷
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //被喚醒后去搶鎖,搶到后繼續(xù)執(zhí)行
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
             //如果阻塞中發(fā)生了中斷,則拋出異常
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
        
(1)addConditionWaiter

在condition等待隊列尾部加入一個節(jié)點

            private Node addConditionWaiter() {
            Node t = lastWaiter;
            // 如果最后一個節(jié)點不是condition狀態(tài)(被取消狀態(tài))被取消狀態(tài)是在fullyReleas方法中產生的
            if (t != null && t.waitStatus != Node.CONDITION) {
                //從頭節(jié)點開始將被取消或者超時的節(jié)點移出隊列
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            //隊列為空的情況  
                if (t == null)
                firstWaiter = node;
            else
                //插入尾節(jié)點
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
(2)fullyRelease

能調用wait方法說明已經獲取到鎖了,fullyRelease方法就是提前調用解鎖方法,將自己從lock的隊列中移出,并返回當前節(jié)點的狀態(tài)savedState,這里如果釋放失敗說明當前線程不在持有鎖,狀態(tài)錯誤,將節(jié)點設置成CANCELLED狀態(tài)

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;
    }
}

release方法調用tryRelease釋放鎖并喚醒首節(jié)點,在ReentrantLock的實現中tryRelease會判斷當前線程是否獲取鎖,所以在lock方法范圍內使用condition會報IllegalMonitorStateException異常

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

回到await方法,循環(huán)調用isOnSyncQueue判斷是否在鎖的等待隊列中(注意不是condition的等待隊列),不在鎖的等待隊列中則調用LockSupport.park(this)掛起線程。

        final boolean 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);
    }

awaitNanos方法

大致邏輯和await相同,就是多了一個時間的判斷

        
        public final long awaitNanos(long nanosTimeout)
                throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            final long deadline = System.nanoTime() + nanosTimeout;
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                    //如果時間小于0,直接從condition隊列
                if (nanosTimeout <= 0L) {
                    transferAfterCancelledWait(node);
                    break;
                }
                //如果大于自旋的閾值則使用parkNanos設置線程掛起的時間,否則繼續(xù)自旋
                if (nanosTimeout >= spinForTimeoutThreshold)
                    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 deadline - System.nanoTime();
        }

signal()方法

signal的作用是將condition隊列中等待時間最長的node轉移到鎖隊列末尾,去重新搶鎖


        public final void signal() {
                //有不同的實現,ReentrantLock中是判斷持有鎖的是否當前線程
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
doSignal

將condition中等待時間最長的節(jié)點調用transferForSignal方法放到鎖隊列中,循環(huán)調用是要尋找第一個不是cancelled狀態(tài)的節(jié)點

       
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
doSignalAll

doSignalAll是將所有等待隊列中的節(jié)點放到鎖隊列末尾

       
        private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }
transferForSignal
    final boolean transferForSignal(Node node) {
      
        //cas設置節(jié)點為0狀態(tài),如果失敗說明節(jié)點已經被取消了
        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).
         */
        //添加到鎖隊列中
        Node p = enq(node);
        int ws = p.waitStatus;
        //cancelled狀態(tài)或者設置SIGNAL狀態(tài)失敗則喚醒此線程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

condition中有很多線程與中斷的細節(jié)處理,有興趣的可以自己去看看源碼

總結一下:

  • condition必須使用在lock中
  • condition提供了類似object.wait和notify的通信機制,但支持多個條件隊列,使用上更靈活
  • condition的原理流程如下
    • 線程1獲取鎖
    • 線程1調用condition.await()進入condition等待隊列并阻塞,釋放鎖給別的線程
    • 線程2獲取鎖,調用condition.signal,將condition等待隊列中的線程1所在的node放在鎖的等待隊列中競爭鎖
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容