java中怎么利用ConcurrentLinkedQueue實(shí)現(xiàn)并發(fā)

本篇文章給大家分享的是有關(guān)java中怎么利用ConcurrentLinkedQueue實(shí)現(xiàn)并發(fā),小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

10年的高昌網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。全網(wǎng)整合營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整高昌建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“高昌網(wǎng)站設(shè)計(jì)”,“高昌網(wǎng)站推廣”以來,每個客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

在并發(fā)編程中,我們可能經(jīng)常需要用到線程安全的隊(duì)列,java為此提供了兩種模式的隊(duì)列:阻塞隊(duì)列和非阻塞隊(duì)列。

:阻塞隊(duì)列和非阻塞隊(duì)列如何實(shí)現(xiàn)線程安全?

  • 阻塞隊(duì)列可以用一個鎖(入隊(duì)和出隊(duì)共享一把鎖)或者兩個鎖(入隊(duì)使用一把鎖,出隊(duì)使用一把鎖)來實(shí)現(xiàn)線程安全,JDK中典型的實(shí)現(xiàn)是BlockingQueue;

  • 非阻塞隊(duì)列可以用循環(huán)CAS的方式來保證數(shù)據(jù)的一致性,來達(dá)到線程安全的目的。

接下來我們就來看看JDK是如何使用非阻塞的方式來實(shí)現(xiàn)線程安全隊(duì)列ConcurrentLinkedQueue的。

ConcurrentLinkedQueue源碼分析

ConcurrentLinkedQueue是一個基于鏈接節(jié)點(diǎn)的無界線程安全隊(duì)列,遵循隊(duì)列的FIFO原則,隊(duì)尾入隊(duì),隊(duì)首出隊(duì)。我們先來看下隊(duì)列的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)以及初始化相關(guān)源碼實(shí)現(xiàn)。

隊(duì)列基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)Node及初始化
  • Node實(shí)現(xiàn)

    Node實(shí)現(xiàn)


    從源碼可以看出,Node有兩個私有屬性item和指向下一個節(jié)點(diǎn)的next,為了降低開銷,item和next都被聲明成volatile類型,同時,它們使用CAS來保證更新的線程安全。

     

  • ConcurrentLinkedQueue數(shù)據(jù)結(jié)構(gòu)

    ConcurrentLinkedQueue私有屬性


    ConcurrentLinkedQueue由head和tail節(jié)點(diǎn)組成,節(jié)點(diǎn)與節(jié)點(diǎn)之間通過next連接,從而來組成一個鏈表結(jié)構(gòu)的隊(duì)列。

     

  • 隊(duì)列初始化
    ConcurrentLinkedQueue有兩個私有屬性,head和tail:

    ConcurrentLinkedQueue私有屬性


    接下來我們來看看ConcurrentLinkedQueue是如何初始化的。

    ConcurrentLinkedQueue初始化


    從源碼可以看出,當(dāng)初始化一個ConcurrentLinkedQueue對象時,會創(chuàng)建一個item和next都為null的節(jié)點(diǎn),并讓head和tail都指向該節(jié)點(diǎn),初始化后的隊(duì)列結(jié)構(gòu)如下:

    初始化后結(jié)構(gòu)圖

     

看完數(shù)據(jù)結(jié)構(gòu)及初始化后,接下里我們就該來看看隊(duì)列的兩個重要實(shí)現(xiàn):入隊(duì)和出隊(duì)。

ConcurrentLinkedQueue入隊(duì)

offer實(shí)現(xiàn)

入隊(duì)的實(shí)質(zhì)就是在隊(duì)尾做節(jié)點(diǎn)插入,具體的執(zhí)行流程如下:

  1. 調(diào)用checkNotNull方法判斷待入隊(duì)元素是否為null,如果為null則拋出空指針異常;

  2. 創(chuàng)建一個待入隊(duì)節(jié)點(diǎn);

  3. 循環(huán)執(zhí)行隊(duì)尾插入:

    • CAS設(shè)置成功:比較p和t,如果p不等t,將tail節(jié)點(diǎn)設(shè)置為待入隊(duì)節(jié)點(diǎn),入隊(duì)成功,返回true,如果p等于t,直接返回true;

    • CAS設(shè)置不成功,表明有其他的線程對tail節(jié)點(diǎn)有所更改,那么,繼續(xù)執(zhí)行for循環(huán),直到入隊(duì)成功。

    • 情況1:如果tail節(jié)點(diǎn)的下一個節(jié)點(diǎn)q為null,通過p.casNext(null, newNode)將p節(jié)點(diǎn)的next節(jié)點(diǎn)設(shè)置為待入隊(duì)節(jié)點(diǎn):

    • 情況3:變換一下順序,先說最后一種情況,改寫一下代碼,就比較容易理解了:

       

      更改后代碼

       

      可以看出來,如果p和t相等,則將p指向q,否則,判斷tail節(jié)點(diǎn)是否發(fā)生變化,如果沒有發(fā)生變化,將p指向q,如果發(fā)生變化,設(shè)置p為尾節(jié)點(diǎn);

    • 情況2:通過情況3的操作,p和q相等的情況就可能會出現(xiàn)了,此時,若tail節(jié)點(diǎn)沒有發(fā)生變化,那么應(yīng)該就是head節(jié)點(diǎn)發(fā)生了變化,設(shè)置p為head節(jié)點(diǎn),從頭開始遍歷隊(duì)列,如果是tail節(jié)點(diǎn)發(fā)生變化,設(shè)置p為tail節(jié)點(diǎn)。

到這里為止,入隊(duì)的源碼分析差不多,說實(shí)在的,還是很懵,大家心里可能可能還在糾結(jié),第一,入隊(duì)的過程到底是什么樣子的呀?第二,入隊(duì)直接CAS更新tail節(jié)點(diǎn)就可以了,為什么還要那么費(fèi)勁的分情況處理?

針對第一個問題,給出一個圖,大家就能完全明白了:

隊(duì)列入隊(duì)結(jié)構(gòu)變化圖.jpg

  1. 添加節(jié)點(diǎn)1,此時tail節(jié)點(diǎn)的next節(jié)點(diǎn)為null,符合上述代碼中的情況1,更新隊(duì)列的tail節(jié)點(diǎn)的next節(jié)點(diǎn)為元素1,由于初始化是head節(jié)點(diǎn)等于tail節(jié)點(diǎn),所以此時head和tail的next節(jié)點(diǎn)均指向節(jié)點(diǎn)1;

  2. 添加節(jié)點(diǎn)2,此時tail節(jié)點(diǎn)的next節(jié)點(diǎn)不為null,同時p也不等于q,符合上述代碼中的情況3,首先執(zhí)行情況3將p指向tail節(jié)點(diǎn)的next節(jié)點(diǎn),再執(zhí)行情況1相關(guān)邏輯,設(shè)置節(jié)點(diǎn)1的next節(jié)點(diǎn)為元素2,此時p不等于t,更新tail節(jié)點(diǎn),將tail節(jié)點(diǎn)指向元素2;

節(jié)點(diǎn)3和4入隊(duì)過程與1和2入隊(duì)一致,在這里我就不再做贅述。需要注意的是:tail節(jié)點(diǎn)并不一定是指向隊(duì)列的最后一個節(jié)點(diǎn),它可能指向最后一個節(jié)點(diǎn)的前一個節(jié)點(diǎn)?。?!

針對第二個問題,我們來嘗試換一種方式思考,假如我們每次就讓tail節(jié)點(diǎn)作為隊(duì)尾節(jié)點(diǎn),每次的入隊(duì)所要做的事情其實(shí)就是將入隊(duì)節(jié)點(diǎn)設(shè)置成尾節(jié)點(diǎn),代碼可以簡化成這樣:

tail為隊(duì)尾節(jié)點(diǎn)的入隊(duì)


上述代碼量確實(shí)非常少,而且邏輯非常清楚和易懂,但是這樣做有個缺點(diǎn)就是每次入隊(duì)都需要循環(huán)CAS更新tail節(jié)點(diǎn)。
如果能減少更新tail節(jié)點(diǎn)的次數(shù),那么就能提高入隊(duì)的效率,所以,Doug Lea并沒有讓tail節(jié)點(diǎn)作為隊(duì)尾節(jié)點(diǎn),只有tail節(jié)點(diǎn)與隊(duì)尾節(jié)點(diǎn)之間的距離等于1的時候才需要更新tail節(jié)點(diǎn)。但是,這樣就可能導(dǎo)致當(dāng)隊(duì)列長度越長的時候每次入隊(duì)定位尾節(jié)點(diǎn)的時間就會越長,即便是這樣,它仍然可以提高入隊(duì)效率,因?yàn)閺谋举|(zhì)上來看,volatile變量的寫操作的開銷要遠(yuǎn)遠(yuǎn)大于讀操作的。

分析完入隊(duì),接下來我們來看看ConcurrentLinkedQueue的出隊(duì)。

ConcurrentLinkedQueue出隊(duì)

poll實(shí)現(xiàn)

出隊(duì)的實(shí)質(zhì)就是情況表頭節(jié)點(diǎn)的引用并返回表頭節(jié)點(diǎn)的值,具體的之行邏輯如下:

  1. 獲取頭結(jié)點(diǎn)的元素;

  2. 如果表頭節(jié)點(diǎn)的元素不為null,并且調(diào)用p.casItem(item, null)設(shè)置表頭節(jié)點(diǎn)數(shù)據(jù)為null成功:

    • 如果p不等于head節(jié)點(diǎn),此時表頭發(fā)生了變化,調(diào)用updateHead方法更新表頭節(jié)點(diǎn),然后返回刪除節(jié)點(diǎn)item;

    • 否則,不更新表頭節(jié)點(diǎn),直接返回刪除節(jié)點(diǎn)item。

  3. 如果步驟2條件不成立并且表頭節(jié)點(diǎn)的next節(jié)點(diǎn)q為null,那么此時隊(duì)列只有一個為null的節(jié)點(diǎn),調(diào)用updateHead方法更新表頭節(jié)點(diǎn)為p,然后返回null;

  4. 如果步驟2和3的條件均不成立并且p等于q,跳轉(zhuǎn)到restartFromHead標(biāo)記重新執(zhí)行;

  5. 步驟2,3,4均不成立,將p指向q;

  6. 循環(huán)執(zhí)行上述步驟;

源碼其實(shí)就這么多,為了方便理解出隊(duì)的過程,還是照樣給一個圖:

隊(duì)列出隊(duì)結(jié)構(gòu)變化圖

  1. 節(jié)點(diǎn)1出隊(duì),此時head的item為null,執(zhí)行上述代碼邏輯中的步驟5,p指向節(jié)點(diǎn)1,此時p的item域不為空,執(zhí)行步驟2,將節(jié)點(diǎn)1的item設(shè)置為null,此時p不等于h,更新頭結(jié)點(diǎn)(如果p的next節(jié)點(diǎn)不為null,將頭結(jié)點(diǎn)指向q,否則指向p),返回節(jié)點(diǎn)1的item,head執(zhí)行節(jié)點(diǎn)2;

  2. 節(jié)點(diǎn)2出隊(duì),此時head的item不為null,執(zhí)行上述代碼邏輯中步驟2,將節(jié)點(diǎn)2的item設(shè)置為null,此時p等于h,直接返回節(jié)點(diǎn)2的item,head仍然指向節(jié)點(diǎn)2;

節(jié)點(diǎn)3和4出隊(duì)過程與1和2出隊(duì)一致,在這里我就不再做贅述。需要注意的是head節(jié)點(diǎn)并不一定是指向隊(duì)列的第一個有效節(jié)點(diǎn),它可能指向有效節(jié)點(diǎn)的前一個節(jié)點(diǎn)!??!

注:這里的有效節(jié)點(diǎn)是指從head節(jié)點(diǎn)向后遍歷可達(dá)的節(jié)點(diǎn)當(dāng)中,item不為null的節(jié)點(diǎn)。

當(dāng)然,為什么head節(jié)點(diǎn)不總是指向隊(duì)列的第一個有效節(jié)點(diǎn),其原因跟入隊(duì)是一樣的,這么做的最主要也是減少CAS更新head節(jié)點(diǎn)的次數(shù),從而提高出隊(duì)效率。

以上就是java中怎么利用ConcurrentLinkedQueue實(shí)現(xiàn)并發(fā),小編相信有部分知識點(diǎn)可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

新聞標(biāo)題:java中怎么利用ConcurrentLinkedQueue實(shí)現(xiàn)并發(fā)
URL分享:http://muchs.cn/article30/gphdpo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版做網(wǎng)站、品牌網(wǎng)站設(shè)計(jì)、定制網(wǎng)站品牌網(wǎng)站制作、品牌網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名