成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入淺出AQS之條件隊(duì)列

VEIGHTZ / 2062人閱讀

摘要:從上面的代碼可以看出,條件隊(duì)列是建立在鎖基礎(chǔ)上的,而且必須是獨(dú)占鎖原因后面會(huì)通過源碼分析。明天就是國慶長假了,我自己也計(jì)劃出國玩一趟,散散心。提前祝廣大朋友國慶快樂。

相比于獨(dú)占鎖跟共享鎖,AbstractQueuedSynchronizer中的條件隊(duì)列可能被關(guān)注的并不是很多,但它在阻塞隊(duì)列的實(shí)現(xiàn)里起著至關(guān)重要的作用,同時(shí)如果想全面了解AQS,條件隊(duì)列也是必須要學(xué)習(xí)的。

原文地址:http://www.jianshu.com/p/3f8b...

這篇文章會(huì)涉及到AQS中獨(dú)占鎖跟共享鎖的一些知識(shí),如果你已經(jīng)對(duì)這兩塊內(nèi)容很了解了,那就直接往下看。否則在讀本文之前還是建議讀者先去看看我之前寫的兩篇文章溫習(xí)一下。
深入淺出AQS之獨(dú)占鎖模式
深入淺出AQS之共享鎖模式

一、使用場(chǎng)景介紹

區(qū)別于前面兩篇文章,可能之前很多人都沒有太在意AQS中的這塊內(nèi)容,所以這篇文章我們先來看下條件隊(duì)列的使用場(chǎng)景:

//首先創(chuàng)建一個(gè)可重入鎖,它本質(zhì)是獨(dú)占鎖
private final ReentrantLock takeLock = new ReentrantLock();
//創(chuàng)建該鎖上的條件隊(duì)列
private final Condition notEmpty = takeLock.newCondition();
//使用過程
public E take() throws InterruptedException {
        //首先進(jìn)行加鎖
        takeLock.lockInterruptibly();
        try {
            //如果隊(duì)列是空的,則進(jìn)行等待
            notEmpty.await();
            //取元素的操作...
            
            //如果有剩余,則喚醒等待元素的線程
            notEmpty.signal();
        } finally {
            //釋放鎖
            takeLock.unlock();
        }
        //取完元素以后喚醒等待放入元素的線程
    }

上面的代碼片段截取自LinkedBlockingQueue,是Java常用的阻塞隊(duì)列之一。
從上面的代碼可以看出,條件隊(duì)列是建立在鎖基礎(chǔ)上的,而且必須是獨(dú)占鎖(原因后面會(huì)通過源碼分析)。

二、執(zhí)行過程概述

等待條件的過程:

在操作條件隊(duì)列之前首先需要成功獲取獨(dú)占鎖,不然直接在獲取獨(dú)占鎖的時(shí)候已經(jīng)被掛起了。

成功獲取獨(dú)占鎖以后,如果當(dāng)前條件還不滿足,則在當(dāng)前鎖的條件隊(duì)列上掛起,與此同時(shí)釋放掉當(dāng)前獲取的鎖資源。這里可以考慮一下如果不釋放鎖資源會(huì)發(fā)生什么?

如果被喚醒,則檢查是否可以獲取獨(dú)占鎖,否則繼續(xù)掛起。

條件滿足后的喚醒過程(以喚醒一個(gè)節(jié)點(diǎn)為例,也可以喚醒多個(gè)):

把當(dāng)前等待隊(duì)列中的第一個(gè)有效節(jié)點(diǎn)(如果被取消就無效了)加入同步隊(duì)列等待被前置節(jié)點(diǎn)喚醒,如果此時(shí)前置節(jié)點(diǎn)被取消,則直接喚醒該節(jié)點(diǎn)讓它重新在同步隊(duì)列里適當(dāng)?shù)膰L試獲取鎖或者掛起。

注:說到這里必須要解釋一個(gè)知識(shí)點(diǎn),整個(gè)AQS分為兩個(gè)隊(duì)列,一個(gè)同步隊(duì)列,一個(gè)條件隊(duì)列。只有同步隊(duì)列中的節(jié)點(diǎn)才能獲取鎖。前面兩篇獨(dú)占鎖共享鎖文章中提到的加入隊(duì)列就是同步隊(duì)列。條件隊(duì)列中所謂的喚醒是把節(jié)點(diǎn)從條件隊(duì)列移到同步隊(duì)列,讓節(jié)點(diǎn)有機(jī)會(huì)去獲取鎖。

二、源碼深入分析

下面的代碼稍微復(fù)雜一點(diǎn),因?yàn)樗紤]了中斷的處理情況。我由于想跟文章開頭的代碼片段保持一致,所以選取了該方法進(jìn)行說明。如果只想看核心邏輯的話,那推薦讀者看看awaitUninterruptibly()方法的源碼。

        //條件隊(duì)列入口,參考上面的代碼片段
        public final void await() throws InterruptedException {
            //如果當(dāng)前線程被中斷則直接拋出異常
            if (Thread.interrupted())
                throw new InterruptedException();
            //把當(dāng)前節(jié)點(diǎn)加入條件隊(duì)列
            Node node = addConditionWaiter();
            //釋放掉已經(jīng)獲取的獨(dú)占鎖資源
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //如果不在同步隊(duì)列中則不斷掛起
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                //中斷處理,另一種跳出循環(huán)的方式
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //走到這里說明節(jié)點(diǎn)已經(jīng)條件滿足被加入到了同步隊(duì)列中或者中斷了
            //這個(gè)方法很熟悉吧?就跟獨(dú)占鎖調(diào)用同樣的獲取鎖方法,從這里可以看出條件隊(duì)列只能用于獨(dú)占鎖
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            //走到這里說明已經(jīng)成功獲取到了獨(dú)占鎖,接下來就做些收尾工作
            //刪除條件隊(duì)列中被取消的節(jié)點(diǎn)
            if (node.nextWaiter != null) 
                unlinkCancelledWaiters();
            //根據(jù)不同模式處理中斷
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

流程比較復(fù)雜,一步一步來分析,首先看下加入條件隊(duì)列的代碼:

        //注:1.與同步隊(duì)列不同,條件隊(duì)列頭尾指針是firstWaiter跟lastWaiter
        //注:2.條件隊(duì)列是在獲取鎖之后,也就是臨界區(qū)進(jìn)行操作,因此很多地方不用考慮并發(fā)
        private Node addConditionWaiter() {
            Node t = lastWaiter;
            //如果最后一個(gè)節(jié)點(diǎn)被取消,則刪除隊(duì)列中被取消的節(jié)點(diǎn)
            //至于為啥是最后一個(gè)節(jié)點(diǎn)后面會(huì)分析
            if (t != null && t.waitStatus != Node.CONDITION) {
                //刪除所有被取消的節(jié)點(diǎn)
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //創(chuàng)建一個(gè)類型為CONDITION的節(jié)點(diǎn)并加入隊(duì)列,由于在臨界區(qū),所以這里不用并發(fā)控制
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

        //刪除取消節(jié)點(diǎn)的邏輯雖然長,但比較簡單,就不多帶帶說了,就是鏈表刪除
        private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

把節(jié)點(diǎn)加入到條件隊(duì)列中以后,接下來要做的就是釋放鎖資源:

    //入?yún)⒕褪切聞?chuàng)建的節(jié)點(diǎn),即當(dāng)前節(jié)點(diǎn)
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            //這里這個(gè)取值要注意,獲取當(dāng)前的state并釋放,這從另一個(gè)角度說明必須是獨(dú)占鎖
            //可以考慮下這個(gè)邏輯放在共享鎖下面會(huì)發(fā)生什么?
            int savedState = getState();
            //跟獨(dú)占鎖釋放鎖資源一樣,不贅述
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                //如果這里釋放失敗,則拋出異常
                throw new IllegalMonitorStateException();
            }
        } finally {
            //如果釋放鎖失敗,則把節(jié)點(diǎn)取消,由這里就能看出來上面添加節(jié)點(diǎn)的邏輯中只需要判斷最后一個(gè)節(jié)點(diǎn)是否被取消就可以了
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

走到這一步,節(jié)點(diǎn)也加入條件隊(duì)列中了,鎖資源也釋放了,接下來就該掛起了(先忽略中斷處理,單看掛起邏輯):

     //如果不在同步隊(duì)列就繼續(xù)掛起(signal操作會(huì)把節(jié)點(diǎn)加入同步隊(duì)列)
     while (!isOnSyncQueue(node)) {
           LockSupport.park(this);
           //中斷處理后面再分析
           if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
     }
    //判斷節(jié)點(diǎn)是否在同步隊(duì)列中
    final boolean isOnSyncQueue(Node node) {
        //快速判斷1:節(jié)點(diǎn)狀態(tài)或者節(jié)點(diǎn)沒有前置節(jié)點(diǎn)
        //注:同步隊(duì)列是有頭節(jié)點(diǎn)的,而條件隊(duì)列沒有
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        //快速判斷2:next字段只有同步隊(duì)列才會(huì)使用,條件隊(duì)列中使用的是nextWaiter字段
        if (node.next != null) 
            return true;
        //上面如果無法判斷則進(jìn)入復(fù)雜判斷
        return findNodeFromTail(node);
    }

    //注意這里用的是tail,這是因?yàn)闂l件隊(duì)列中的節(jié)點(diǎn)是被加入到同步隊(duì)列尾部,這樣查找更快
    //從同步隊(duì)列尾節(jié)點(diǎn)開始向前查找當(dāng)前節(jié)點(diǎn),如果找到則說明在,否則不在
    private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

如果被喚醒且已經(jīng)被轉(zhuǎn)移到了同步隊(duì)列,則會(huì)執(zhí)行與獨(dú)占鎖一樣的方法acquireQueued()進(jìn)行同步隊(duì)列獨(dú)占獲取。
最后我們來梳理一下里面的中斷邏輯以及收尾工作的代碼:

     while (!isOnSyncQueue(node)) {
           LockSupport.park(this);
           //這里被喚醒可能是正常的signal操作也可能是中斷
           if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
     }

     //這里的判斷邏輯是:
     //1.如果現(xiàn)在不是中斷的,即正常被signal喚醒則返回0
     //2.如果節(jié)點(diǎn)由中斷加入同步隊(duì)列則返回THROW_IE,由signal加入同步隊(duì)列則返回REINTERRUPT
     private int checkInterruptWhileWaiting(Node node) {
           return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
     }

     //修改節(jié)點(diǎn)狀態(tài)并加入同步隊(duì)列
     //該方法返回true表示節(jié)點(diǎn)由中斷加入同步隊(duì)列,返回false表示由signal加入同步隊(duì)列
     final boolean transferAfterCancelledWait(Node node) {
        //這里設(shè)置節(jié)點(diǎn)狀態(tài)為0,如果成功則加入同步隊(duì)列
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            //與獨(dú)占鎖同樣的加入隊(duì)列邏輯,不贅述
            enq(node);
            return true;
        }
        //如果上面設(shè)置失敗,說明節(jié)點(diǎn)已經(jīng)被signal喚醒,由于signal操作會(huì)將節(jié)點(diǎn)加入同步隊(duì)列,我們只需自旋等待即可
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
     }

在把喚醒后的中斷判斷做好以后,看await()中最后一段邏輯:

//在處理中斷之前首先要做的是從同步隊(duì)列中成功獲取鎖資源
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
      interruptMode = REINTERRUPT;
//由于當(dāng)前節(jié)點(diǎn)可能是由于中斷修改了節(jié)點(diǎn)狀態(tài),所以如果有后繼節(jié)點(diǎn)則執(zhí)行刪除已取消節(jié)點(diǎn)的操作
//如果沒有后繼節(jié)點(diǎn),根據(jù)上面的分析在后繼節(jié)點(diǎn)加入的時(shí)候會(huì)進(jìn)行刪除
if (node.nextWaiter != null) 
      unlinkCancelledWaiters();
if (interruptMode != 0)
      reportInterruptAfterWait(interruptMode);

//根據(jù)中斷時(shí)機(jī)選擇拋出異?;蛘咴O(shè)置線程中斷狀態(tài)
private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
      if (interruptMode == THROW_IE)
           throw new InterruptedException();
      else if (interruptMode == REINTERRUPT)
           //實(shí)現(xiàn)代碼為:Thread.currentThread().interrupt();
           selfInterrupt();
}

至此條件隊(duì)列await操作全部分析完畢。signal()方法相對(duì)容易一些,一起看源碼分析下:

   //條件隊(duì)列喚醒入口
   public final void signal() {
       //如果不是獨(dú)占鎖則拋出異常,再次說明條件隊(duì)列只適用于獨(dú)占鎖
       if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
       //如果條件隊(duì)列不為空,則進(jìn)行喚醒操作
       Node first = firstWaiter;
       if (first != null)
            doSignal(first);
   }

   //該方法就是把一個(gè)有效節(jié)點(diǎn)從條件隊(duì)列中刪除并加入同步隊(duì)列
   //如果失敗則會(huì)查找條件隊(duì)列上等待的下一個(gè)節(jié)點(diǎn)直到隊(duì)列為空
   private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) &&(first = firstWaiter) != null);
   }

    //將節(jié)點(diǎn)加入同步隊(duì)列
    final boolean transferForSignal(Node node) {
        //修改節(jié)點(diǎn)狀態(tài),這里如果修改失敗只有一種可能就是該節(jié)點(diǎn)被取消,具體看上面await過程分析
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //該方法很熟悉了,跟獨(dú)占鎖入隊(duì)方法一樣,不贅述
        Node p = enq(node);
        //注:這里的p節(jié)點(diǎn)是當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)
        int ws = p.waitStatus;
        //如果前置節(jié)點(diǎn)被取消或者修改狀態(tài)失敗則直接喚醒當(dāng)前節(jié)點(diǎn)
        //此時(shí)當(dāng)前節(jié)點(diǎn)已經(jīng)處于同步隊(duì)列中,喚醒會(huì)進(jìn)行鎖獲取或者正確的掛起操作
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
三、總結(jié)

相比于獨(dú)占鎖跟共享鎖,條件隊(duì)列可能是最不受關(guān)注的了,但由于它是阻塞隊(duì)列實(shí)現(xiàn)的關(guān)鍵組件,還是有必要了解一下其中的原理。其實(shí)我認(rèn)為關(guān)鍵點(diǎn)有兩條,第一是條件隊(duì)列是建立在某個(gè)具體的鎖上面的,第二是條件隊(duì)列跟同步隊(duì)列是兩個(gè)隊(duì)列,前者依賴條件喚醒后者依賴鎖釋放喚醒,了解了這兩點(diǎn)以后搞清楚條件隊(duì)列就不是什么難事了。

至此,Java同步器AQS中三大鎖模式就都分析完了。雖然已經(jīng)盡力思考,盡量寫的清楚,但鑒于水平有限,如果有紕漏的地方,歡迎廣大讀者指正。
明天就是國慶長假了,我自己也計(jì)劃出國玩一趟,散散心。
提前祝廣大朋友國慶快樂。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/67653.html

相關(guān)文章

  • 深入淺出AQS組件概覽

    摘要:原文地址深入淺出之獨(dú)占鎖模式深入淺出之共享鎖模式深入淺出之條件隊(duì)列前面三篇文章如果之前沒有基礎(chǔ)的話看起來會(huì)比較吃力,這篇文章說明一下的基礎(chǔ)知識(shí),方便快速了解。當(dāng)前節(jié)點(diǎn)由于超時(shí)或者中斷被取消,節(jié)點(diǎn)進(jìn)入這個(gè)狀態(tài)以后將保持不變。 之前分析了AQS中的獨(dú)占鎖,共享鎖,條件隊(duì)列三大模塊,現(xiàn)在從結(jié)構(gòu)上來看看AQS各個(gè)組件的情況。 原文地址:http://www.jianshu.com/p/49b8...

    DDreach 評(píng)論0 收藏0
  • 深入淺出AQS獨(dú)占鎖模式

    摘要:獲取鎖的過程當(dāng)線程調(diào)用申請(qǐng)獲取鎖資源,如果成功,則進(jìn)入臨界區(qū)。如果隊(duì)列中有其他等待鎖資源的線程需要喚醒,則喚醒隊(duì)列中的第一個(gè)等待節(jié)點(diǎn)先入先出。釋放鎖時(shí),如果隊(duì)列中有等待的線程就進(jìn)行喚醒。 每一個(gè)Java工程師應(yīng)該都或多或少了解過AQS,我自己也是前前后后,反反復(fù)復(fù)研究了很久,看了忘,忘了再看,每次都有不一樣的體會(huì)。這次趁著寫博客,打算重新拿出來系統(tǒng)的研究下它的源碼,總結(jié)成文章,便于以后...

    Corwien 評(píng)論0 收藏0
  • 源碼分析JDK8AbstractQueuedSynchronizer

    摘要:與之相關(guān)的方法有三個(gè)原子性地修改都是類型,可見我們可以進(jìn)行,來定義的獲取與釋放從而實(shí)現(xiàn)我們自定義的同步器。 前言 源碼分析我認(rèn)為主要有兩個(gè)作用:滿足好奇心,我想每一個(gè)有追求的人都不會(huì)滿足于僅僅做一個(gè)API Caller實(shí)現(xiàn)功能就好,我們也想知道它到底是怎么實(shí)現(xiàn)的;借鑒與升華,當(dāng)我們明白了一個(gè)類的設(shè)計(jì)原理,在一定的情境下我們可以借鑒其設(shè)計(jì)哲學(xué),甚至針對(duì)我們自己特殊的業(yè)務(wù)場(chǎng)景對(duì)其進(jìn)行改良與...

    魏憲會(huì) 評(píng)論0 收藏0
  • 源碼分析JDK8AbstractQueuedSynchronizer

    摘要:與之相關(guān)的方法有三個(gè)原子性地修改都是類型,可見我們可以進(jìn)行,來定義的獲取與釋放從而實(shí)現(xiàn)我們自定義的同步器。 前言 源碼分析我認(rèn)為主要有兩個(gè)作用:滿足好奇心,我想每一個(gè)有追求的人都不會(huì)滿足于僅僅做一個(gè)API Caller實(shí)現(xiàn)功能就好,我們也想知道它到底是怎么實(shí)現(xiàn)的;借鑒與升華,當(dāng)我們明白了一個(gè)類的設(shè)計(jì)原理,在一定的情境下我們可以借鑒其設(shè)計(jì)哲學(xué),甚至針對(duì)我們自己特殊的業(yè)務(wù)場(chǎng)景對(duì)其進(jìn)行改良與...

    sunny5541 評(píng)論0 收藏0
  • 源碼分析JDK8AbstractQueuedSynchronizer

    摘要:與之相關(guān)的方法有三個(gè)原子性地修改都是類型,可見我們可以進(jìn)行,來定義的獲取與釋放從而實(shí)現(xiàn)我們自定義的同步器。 前言 源碼分析我認(rèn)為主要有兩個(gè)作用:滿足好奇心,我想每一個(gè)有追求的人都不會(huì)滿足于僅僅做一個(gè)API Caller實(shí)現(xiàn)功能就好,我們也想知道它到底是怎么實(shí)現(xiàn)的;借鑒與升華,當(dāng)我們明白了一個(gè)類的設(shè)計(jì)原理,在一定的情境下我們可以借鑒其設(shè)計(jì)哲學(xué),甚至針對(duì)我們自己特殊的業(yè)務(wù)場(chǎng)景對(duì)其進(jìn)行改良與...

    Betta 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<