<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    xylz,imxylz

    關(guān)注后端架構(gòu)、中間件、分布式和并發(fā)編程

       :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      111 隨筆 :: 10 文章 :: 2680 評(píng)論 :: 0 Trackbacks

    ConcurrentLinkedQueue是Queue的一個(gè)線(xiàn)程安全實(shí)現(xiàn)。先來(lái)看一段文檔說(shuō)明。

    一個(gè)基于鏈接節(jié)點(diǎn)的無(wú)界線(xiàn)程安全隊(duì)列。此隊(duì)列按照 FIFO(先進(jìn)先出)原則對(duì)元素進(jìn)行排序。隊(duì)列的頭部 是隊(duì)列中時(shí)間最長(zhǎng)的元素。隊(duì)列的尾部 是隊(duì)列中時(shí)間最短的元素。新的元素插入到隊(duì)列的尾部,隊(duì)列獲取操作從隊(duì)列頭部獲得元素。當(dāng)多個(gè)線(xiàn)程共享訪(fǎng)問(wèn)一個(gè)公共 collection 時(shí),ConcurrentLinkedQueue 是一個(gè)恰當(dāng)?shù)倪x擇。此隊(duì)列不允許使用 null 元素。

     

    由于ConcurrentLinkedQueue只是簡(jiǎn)單的實(shí)現(xiàn)了一個(gè)隊(duì)列Queue,因此從API的角度講,沒(méi)有多少值的介紹,使用起來(lái)也很簡(jiǎn)單,和前面遇到的所有FIFO隊(duì)列都類(lèi)似。出隊(duì)列只能操作頭節(jié)點(diǎn),入隊(duì)列只能操作尾節(jié)點(diǎn),任意節(jié)點(diǎn)操作就需要遍歷完整的隊(duì)列。

    重點(diǎn)放在解釋ConcurrentLinkedQueue的原理和實(shí)現(xiàn)上。

     

    在繼續(xù)探討之前,結(jié)合前面線(xiàn)程安全的相關(guān)知識(shí),我來(lái)分析設(shè)計(jì)一個(gè)線(xiàn)程安全的隊(duì)列哪幾種方法。

    第一種:使用synchronized同步隊(duì)列,就像Vector或者Collections.synchronizedList/Collection那樣。顯然這不是一個(gè)好的并發(fā)隊(duì)列,這會(huì)導(dǎo)致吞吐量急劇下降。

    第二種:使用Lock。一種好的實(shí)現(xiàn)方式是使用ReentrantReadWriteLock來(lái)代替ReentrantLock提高讀取的吞吐量。但是顯然ReentrantReadWriteLock的實(shí)現(xiàn)更為復(fù)雜,而且更容易導(dǎo)致出現(xiàn)問(wèn)題,另外也不是一種通用的實(shí)現(xiàn)方式,因?yàn)镽eentrantReadWriteLock適合哪種讀取量遠(yuǎn)遠(yuǎn)大于寫(xiě)入量的場(chǎng)合。當(dāng)然了ReentrantLock是一種很好的實(shí)現(xiàn),結(jié)合Condition能夠很方便的實(shí)現(xiàn)阻塞功能,這在后面介紹BlockingQueue的時(shí)候會(huì)具體分析。

    第三種:使用CAS操作。盡管Lock的實(shí)現(xiàn)也用到了CAS操作,但是畢竟是間接操作,而且會(huì)導(dǎo)致線(xiàn)程掛起。一個(gè)好的并發(fā)隊(duì)列就是采用某種非阻塞算法來(lái)取得最大的吞吐量。

    ConcurrentLinkedQueue采用的就是第三種策略。它采用了參考資料1 中的算法。

    在鎖機(jī)制中談到過(guò),要使用非阻塞算法來(lái)完成隊(duì)列操作,那么就需要一種“循環(huán)嘗試”的動(dòng)作,就是循環(huán)操作隊(duì)列,直到成功為止,失敗就會(huì)再次嘗試。這在前面的章節(jié)中多次介紹過(guò)。

     

    針對(duì)各種功能深入分析。

    在開(kāi)始之前先介紹下ConcurrentLinkedQueue的數(shù)據(jù)結(jié)構(gòu)。

    image在上面的數(shù)據(jù)結(jié)構(gòu)中,ConcurrentLinkedQueue只有頭結(jié)點(diǎn)、尾節(jié)點(diǎn)兩個(gè)元素,而對(duì)于一個(gè)節(jié)點(diǎn)Node而言除了保存隊(duì)列元素item外,還有一個(gè)指向下一個(gè)節(jié)點(diǎn)的引用next。 看起來(lái)整個(gè)數(shù)據(jù)結(jié)構(gòu)還是比較簡(jiǎn)單的。但是也有幾點(diǎn)是需要說(shuō)明:

    1. 所有結(jié)構(gòu)(head/tail/item/next)都是volatile類(lèi)型。 這是因?yàn)镃oncurrentLinkedQueue是非阻塞的,所以只有volatile才能使變量的寫(xiě)操作對(duì)后續(xù)讀操作是可見(jiàn)的(這個(gè)是有happens-before法則保證的)。同樣也不會(huì)導(dǎo)致指令的重排序。
    2. 所有結(jié)構(gòu)的操作都帶有原子操作,這是由AtomicReferenceFieldUpdater保證的,這在原子操作中介紹過(guò)。它能保證需要的時(shí)候?qū)ψ兞康男薷牟僮魇窃拥摹?
    3. 由于隊(duì)列中任何一個(gè)節(jié)點(diǎn)(Node)只有下一個(gè)節(jié)點(diǎn)的引用,所以這個(gè)隊(duì)列是單向的,根據(jù)FIFO特性,也就是說(shuō)出隊(duì)列在頭部(head),入隊(duì)列在尾部(tail)。頭部保存有進(jìn)入隊(duì)列最長(zhǎng)時(shí)間的元素,尾部是最近進(jìn)入的元素。
    4. 沒(méi)有對(duì)隊(duì)列長(zhǎng)度進(jìn)行計(jì)數(shù),所以隊(duì)列的長(zhǎng)度是無(wú)限的,同時(shí)獲取隊(duì)列的長(zhǎng)度的時(shí)間不是固定的,這需要遍歷整個(gè)隊(duì)列,并且這個(gè)計(jì)數(shù)也可能是不精確的。
    5. 初始情況下隊(duì)列頭和隊(duì)列尾都指向一個(gè)空節(jié)點(diǎn),但是非null,這是為了方便操作,不需要每次去判斷head/tail是否為空。但是head卻不作為存取元素的節(jié)點(diǎn),tail在不等于head情況下保存一個(gè)節(jié)點(diǎn)元素。也就是說(shuō)head.item這個(gè)應(yīng)該一直是空,但是tail.item卻不一定是空(如果head!=tail,那么tail.item!=null)。

    對(duì)于第5點(diǎn),可以從ConcurrentLinkedQueue的初始化中看到。這種頭結(jié)點(diǎn)也叫“偽節(jié)點(diǎn)”,也就是說(shuō)它不是真正的節(jié)點(diǎn),只是一標(biāo)識(shí),就像c中的字符數(shù)組后面的\0以后,只是用來(lái)標(biāo)識(shí)結(jié)束,并不是真正字符數(shù)組的一部分。

    private transient volatile Node<E> head = new Node<E>(null, null);
    private transient volatile Node<E> tail = head;

    有了上述5點(diǎn)再來(lái)解釋相關(guān)API操作就容易多了。

    在上一節(jié)中列出了add/offer/remove/poll/element/peek等價(jià)方法的區(qū)別,所以這里就不再重復(fù)了。

    清單1 入隊(duì)列操作

    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        Node<E> n = new Node<E>(e, null);
        for (;;) {
            Node<E> t = tail;
            Node<E> s = t.getNext();
            if (t == tail) {
                if (s == null) {
                    if (t.casNext(s, n)) {
                        casTail(t, n);
                        return true;
                    }
                } else {
                    casTail(t, s);
                }
            }
        }
    }

    清單1 描述的是入隊(duì)列的過(guò)程。整個(gè)過(guò)程是這樣的。

      1. 獲取尾節(jié)點(diǎn)t,以及尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)s。如果尾節(jié)點(diǎn)沒(méi)有被別人修改,也就是t==tail,進(jìn)行2,否則進(jìn)行1。
      2. 如果s不為空,也就是說(shuō)此時(shí)尾節(jié)點(diǎn)后面還有元素,那么就需要把尾節(jié)點(diǎn)往后移,進(jìn)行1。否則進(jìn)行3。
      3. 修改尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)為新節(jié)點(diǎn),如果成功就修改尾節(jié)點(diǎn),返回true。否則進(jìn)行1。

    從操作3中可以看到是先修改尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),然后才修改尾節(jié)點(diǎn)位置的,所以這才有操作2中為什么獲取到的尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)不為空的原因。

    特別需要說(shuō)明的是,對(duì)尾節(jié)點(diǎn)的tail的操作需要換成臨時(shí)變量t和s,一方面是為了去掉volatile變量的可變性,另一方面是為了減少volatile的性能影響。

     

    清單2 描述的出隊(duì)列的過(guò)程,這個(gè)過(guò)程和入隊(duì)列相似,有點(diǎn)意思。

    頭結(jié)點(diǎn)是為了標(biāo)識(shí)隊(duì)列起始,也為了減少空指針的比較,所以頭結(jié)點(diǎn)總是一個(gè)item為null的非null節(jié)點(diǎn)。也就是說(shuō)head!=null并且head.item==null總是成立。所以實(shí)際上獲取的是head.next,一旦將頭結(jié)點(diǎn)head設(shè)置為head.next成功就將新head的item設(shè)置為null。至于以前就的頭結(jié)點(diǎn)h,h.item=null并且h.next為新的head,但是由于沒(méi)有對(duì)h的引用,所以最終會(huì)被GC回收。這就是整個(gè)出隊(duì)列的過(guò)程。

    清單2 出隊(duì)列操作

    public E poll() {
        for (;;) {
            Node<E> h = head;
            Node<E> t = tail;
            Node<E> first = h.getNext();
            if (h == head) {
                if (h == t) {
                    if (first == null)
                        return null;
                    else
                        casTail(t, first);
                } else if (casHead(h, first)) {
                    E item = first.getItem();
                    if (item != null) {
                        first.setItem(null);
                        return item;
                    }
                    // else skip over deleted item, continue loop,
                }
            }
        }
    }

     

    另外對(duì)于清單3 描述的獲取隊(duì)列大小的過(guò)程,由于沒(méi)有一個(gè)計(jì)數(shù)器來(lái)對(duì)隊(duì)列大小計(jì)數(shù),所以獲取隊(duì)列的大小只能通過(guò)從頭到尾完整的遍歷隊(duì)列,顯然這個(gè)代價(jià)是很大的。所以通常情況下ConcurrentLinkedQueue需要和一個(gè)AtomicInteger搭配才能獲取隊(duì)列大小。后面介紹的BlockingQueue正是使用了這種思想。

    清單3 遍歷隊(duì)列大小

    public int size() {
        int count = 0;
        for (Node<E> p = first(); p != null; p = p.getNext()) {
            if (p.getItem() != null) {
                // Collections.size() spec says to max out
                if (++count == Integer.MAX_VALUE)
                    break;
            }
        }
        return count;
    }

     

     

     

     

    參考資料:

    1. Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms
    2. 多線(xiàn)程基礎(chǔ)總結(jié)十一—ConcurrentLinkedQueue
    3. 對(duì)ConcurrentLinkedQueue進(jìn)行的并發(fā)測(cè)試 

     



    ©2009-2014 IMXYLZ |求賢若渴
    posted on 2010-07-23 14:11 imxylz 閱讀(20016) 評(píng)論(2)  編輯  收藏 所屬分類(lèi): Java Concurrency

    評(píng)論

    # re: 深入淺出 Java Concurrency (20): 并發(fā)容器 part 5 ConcurrentLinkedQueue 2013-12-16 17:09 mashiguang
    為什么java.util.concurrent.ArrayBlockingQueue<E>只用一個(gè)int記錄count, 而java.util.concurrent.LinkedBlockingQueue<E>卻要用一個(gè)AtomicInteger記錄count  回復(fù)  更多評(píng)論
      

    # re: 深入淺出 Java Concurrency (20): 并發(fā)容器 part 5 ConcurrentLinkedQueue 2015-06-01 10:57 liubey
    @mashiguang
    我猜測(cè)應(yīng)該是ArrayBlockingQueue用一把鎖而LinkedBlockingQueue用兩把鎖的原因  回復(fù)  更多評(píng)論
      


    ©2009-2014 IMXYLZ
    主站蜘蛛池模板: 亚洲精品国精品久久99热一| 国产精品无码免费播放| 日韩免费在线视频| 丁香花在线视频观看免费| 国产在线国偷精品免费看| 国产精品玖玖美女张开腿让男人桶爽免费看 | 99免费视频观看| 在线人成精品免费视频| 久久久久av无码免费网| 我们的2018在线观看免费高清 | 国产亚洲成在线播放va| 黄色免费在线网址| jizz18免费视频| 久草免费福利视频| 18禁止看的免费污网站| 皇色在线视频免费网站| 在线视频免费国产成人| 无码欧精品亚洲日韩一区夜夜嗨| 久久久久久A亚洲欧洲AV冫| 亚洲成色在线综合网站| 久久亚洲AV无码精品色午夜麻豆| 久久精品国产亚洲AV蜜臀色欲| 亚洲熟妇无码av另类vr影视| 国产亚洲Av综合人人澡精品| 精品多毛少妇人妻AV免费久久| 国产成人一区二区三区视频免费| 国产福利视精品永久免费| 女人被免费视频网站| 亚洲国产精品尤物yw在线 | 又黄又爽一线毛片免费观看| 伊人久久大香线蕉亚洲| 亚洲色图.com| 国产偷国产偷亚洲清高APP| 国产精品青草视频免费播放| 4虎1515hh永久免费| 国产成人免费手机在线观看视频| 国产亚洲精品拍拍拍拍拍| 亚洲综合激情视频| 久久久久亚洲国产AV麻豆| 日韩av无码免费播放| 国产h视频在线观看免费|