從點(diǎn)到面, 下面我們來(lái)看下非阻塞隊(duì)列經(jīng)典實(shí)現(xiàn)類(lèi)ConcurrentLinkedQueue,需要的朋友可以參考下" />

最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java并發(fā)容器之ConcurrentLinkedQueue詳解

 更新時(shí)間:2023年12月26日 09:51:06   作者:fastjson_  
這篇文章主要介紹了Java并發(fā)容器之ConcurrentLinkedQueue詳解,加鎖隊(duì)列的實(shí)現(xiàn)較為簡(jiǎn)單,這里就略過(guò),我們來(lái)重點(diǎn)來(lái)解讀一下非阻塞隊(duì)列,
從點(diǎn)到面, 下面我們來(lái)看下非阻塞隊(duì)列經(jīng)典實(shí)現(xiàn)類(lèi)ConcurrentLinkedQueue,需要的朋友可以參考下

簡(jiǎn)介

并編程中,一般需要用到安全的隊(duì)列,如果要自己實(shí)現(xiàn)安全隊(duì)列,可以使用2種方式:

  1. 加鎖,這種實(shí)現(xiàn)方式就是我們常說(shuō)的阻塞隊(duì)列。
  2. 使用循環(huán)CAS算法實(shí)現(xiàn),這種方式實(shí)現(xiàn)隊(duì)列稱(chēng)之為非阻塞隊(duì)列。

加鎖隊(duì)列的實(shí)現(xiàn)較為簡(jiǎn)單,這里就略過(guò),我們來(lái)重點(diǎn)來(lái)解讀一下非阻塞隊(duì)列。 從點(diǎn)到面, 下面我們來(lái)看下非阻塞隊(duì)列經(jīng)典實(shí)現(xiàn)類(lèi):ConcurrentLinkedQueue (JDK1.8版)  

看下ConcurrentLinkedQueue的結(jié)構(gòu)圖

從內(nèi)圖可以了解ConcurrentLinkedQueue一個(gè)大概,ConcurrentLinkedQueue內(nèi)部持有2個(gè)節(jié)點(diǎn):head頭結(jié)點(diǎn),負(fù)責(zé)出列, tail尾節(jié)點(diǎn),負(fù)責(zé)入列。

而元素節(jié)點(diǎn)Node,使用item存儲(chǔ)入列元素,next指向下一個(gè)元素節(jié)點(diǎn)。

    private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
        //....
    }
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
        implements Queue<E>, java.io.Serializable {
        private transient volatile Node<E> head;
        private transient volatile Node<E> tail;  
        //....
}

 ConcurrentLinkedQueue使用特點(diǎn)

  • 不允許null入列
  • 在入隊(duì)的最后一個(gè)元素的next為null
  • 隊(duì)列中所有未刪除的節(jié)點(diǎn)的item都不能為null且都能從head節(jié)點(diǎn)遍歷到
  • 刪除節(jié)點(diǎn)是將item設(shè)置為null, 隊(duì)列迭代時(shí)跳過(guò)item為null節(jié)點(diǎn)
  • head節(jié)點(diǎn)跟tail不一定指向頭節(jié)點(diǎn)或尾節(jié)點(diǎn),可能存在滯后性

之所以有這奇葩約定,全因ConcurrentLinkedQueue是并發(fā)非阻塞隊(duì)列決定的。 我們從源碼上看一下ConcurrentLinkedQueue實(shí)現(xiàn)過(guò)程

ConcurrentLinkedQueue源碼詳解

入列

我們印象中鏈表特點(diǎn):tail節(jié)點(diǎn)表示最后一個(gè)節(jié)點(diǎn), head表示第一個(gè)節(jié)點(diǎn)。ConcurrentLinkedQueue 跟傳統(tǒng)的鏈表有點(diǎn)區(qū)別,在單線程環(huán)境下符合傳統(tǒng)鏈表特點(diǎn),但涉及到多線程環(huán)境,ConcurrentLinkedQueue 中的tail節(jié)點(diǎn)不一定是最后一個(gè)節(jié)點(diǎn),他可能是倒數(shù)第二個(gè)。所以ConcurrentLinkedQueue判斷隊(duì)尾條件是節(jié)點(diǎn)的next為null。

   public boolean offer(E e) {
        checkNotNull(e);   //為空判斷,e為null是拋異常
        final Node<E> newNode = new Node<E>(e); //將e包裝成newNode
        for (Node<E> t = tail, p = t;;) {  //循環(huán)cas,直至加入成功
            //t = p = tail 
            Node<E> q = p.next;
            if (q == null) {   //判斷p是否為尾節(jié)點(diǎn)
                //如果是,p.next = newNode
                if (p.casNext(null, newNode)) {
                    //首次添加時(shí),p 等于t,不進(jìn)行尾節(jié)點(diǎn)更新,所以所尾節(jié)點(diǎn)存在滯后性  
                    //并發(fā)環(huán)境,可能存添加/刪除,tail就更難保證正確指向最后節(jié)點(diǎn)。
                    if (p != t) 
                        //更新尾節(jié)點(diǎn)為最新元素
                        casTail(t, newNode);  
                    return true;
                }
            }
            else if (p == q)
                //當(dāng)tail不執(zhí)行最后節(jié)點(diǎn)時(shí),如果執(zhí)行出列操作,很有可能將tail也給移除了    
                //此時(shí)需要對(duì)tail節(jié)點(diǎn)進(jìn)行復(fù)位,復(fù)位到head節(jié)點(diǎn)
                p = (t != (t = tail)) ? t : head;
            else
                //推動(dòng)tail尾節(jié)點(diǎn)往隊(duì)尾移動(dòng)
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

分析

1、初始化

 2、添加A元素

 3、添加B元素

 4、添加C

從圖上看tail不一定執(zhí)行最后一個(gè)節(jié)點(diǎn),但可以確定最后節(jié)點(diǎn)的next節(jié)點(diǎn)為null。

到這可能朋友問(wèn)他,并發(fā)環(huán)境什么情況都有可能,ConcurrentLinkedQueue是怎么保證線程安全的? 我們觀察offer方法的設(shè)計(jì),

1:是一個(gè)死循環(huán),就是不停使用cas判斷直到添加元素入隊(duì)成功。

for (Node<E> t = tail, p = t;;)

2:2個(gè)cas判斷方法 p.casNext(null, newNode) 確保隊(duì)列在入列時(shí)是原子操作

 private boolean casTail(Node<E> cmp, Node<E> val) {
     return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
 }

casTail(t, newNode); 確保tail隊(duì)尾在移動(dòng)改變時(shí)是原子操作

boolean casNext(Node<E> cmp, Node<E> val) {
    return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}

而在并發(fā)環(huán)境,ConcurrentLinkedQueue入列線程安全考慮具體可分2類(lèi):

1>線程1線程2同時(shí)入列 這個(gè)好理解, 線程1,線程2不管在offer哪個(gè)位置開(kāi)始并發(fā),他們最終的目的都是入列,也即都需要執(zhí)行casNext方法, 我們只需要確保所有線程都有機(jī)會(huì)執(zhí)行casNext方法,并且保證casNext方法是原子操作即可。casNext失敗的線程,可以進(jìn)入下一輪循環(huán),人品好的話就可以入列,衰的話繼續(xù)循環(huán)

2>線程1遍歷,線程2入列 ConcurrentLinkedQueue 遍歷是線程不安全的, 線程1遍歷,線程2很有可能進(jìn)行入列出列操作, 所以ConcurrentLinkedQueue 的size是變化。換句話說(shuō),要想安全遍歷ConcurrentLinkedQueue 隊(duì)列,必須額外加鎖。

但換一個(gè)角度想, ConcurrentLinkedQueue 的設(shè)計(jì)初衷非阻塞隊(duì)列,我們更多關(guān)注入列與出列線程安全,這2點(diǎn)能保證就可以啦。

出列

    public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                //入列折騰的tail,那出列折騰的就是head
                E item = p.item;
                //出列判斷依據(jù)是節(jié)點(diǎn)的item=null
                //item != null, 并且能將操作節(jié)點(diǎn)的item設(shè)置null, 表示出列成功
                if (item != null && p.casItem(item, null)) {
                    if (p != h) 
                        //一旦出列成功需要對(duì)head進(jìn)行移動(dòng)
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                else if (p == q)
                    //第一輪操作失敗,下一輪繼續(xù),調(diào)回到循環(huán)前
                    continue restartFromHead;
                else
                    //推動(dòng)head節(jié)點(diǎn)移動(dòng)
                    p = q;
            }
        }
    }

看圖, 被移動(dòng)的節(jié)點(diǎn)(item為null的節(jié)點(diǎn))會(huì)被jvm回收。

但是有個(gè)問(wèn)題, tail也被回收, 那ConcurrentLinkedQueue就沒(méi)有tail節(jié)點(diǎn)了。

如果此時(shí)再添加一個(gè)D元素時(shí),會(huì)出現(xiàn)什么情況?

 好問(wèn)的朋友,又想了,ConcurrentLinkedQueue怎么保證出列線程安全?道理跟之前入列一樣,cas保證原子操作即可。

總結(jié)

到這ConcurrentLinkedQueue介紹就完成了。總結(jié)下ConcurrentLinkedQueue貼點(diǎn):

入列出列線程安全,遍歷不安全不允許添加null元素底層使用列表與cas算法包裝入列出列安全

到此這篇關(guān)于Java并發(fā)容器之ConcurrentLinkedQueue詳解的文章就介紹到這了,更多相關(guān)ConcurrentLinkedQueue詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入理解JVM自動(dòng)內(nèi)存管理

    深入理解JVM自動(dòng)內(nèi)存管理

    對(duì)于Java虛擬機(jī)在內(nèi)存分配與回收的學(xué)習(xí),本文主要介紹了JVM自動(dòng)內(nèi)存管理,文中通過(guò)圖文示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08
  • Java壓縮和解壓縮ZIP文件實(shí)戰(zhàn)案例

    Java壓縮和解壓縮ZIP文件實(shí)戰(zhàn)案例

    這篇文章主要給大家介紹了關(guān)于Java壓縮和解壓縮ZIP文件的相關(guān)資料,ZIP是一種較為常見(jiàn)的壓縮形式,最近項(xiàng)目中遇到了再Java中壓縮和解壓縮zip文件的需求,特此分享給大家,需要的朋友可以參考下
    2023-07-07
  • 為什么JDK8中HashMap依然會(huì)死循環(huán)

    為什么JDK8中HashMap依然會(huì)死循環(huán)

    這篇文章主要介紹了為什么JDK8中HashMap依然會(huì)死循環(huán),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Java Spring 中的監(jiān)聽(tīng)器Listener詳解與實(shí)戰(zhàn)教程

    Java Spring 中的監(jiān)聽(tīng)器Listener詳解與實(shí)戰(zhàn)教程

    Spring 提供了多種監(jiān)聽(tīng)器機(jī)制,可以用于監(jiān)聽(tīng)?wèi)?yīng)用生命周期、會(huì)話生命周期和請(qǐng)求處理過(guò)程中的事件,這篇文章主要介紹了Java Spring 之監(jiān)聽(tīng)器(Listener)詳解與實(shí)戰(zhàn),需要的朋友可以參考下
    2025-06-06
  • Java+mysql本地圖片上傳數(shù)據(jù)庫(kù)及下載示例

    Java+mysql本地圖片上傳數(shù)據(jù)庫(kù)及下載示例

    本篇文章主要介紹了Java+mysql本地圖片上傳數(shù)據(jù)庫(kù)及下載示例,具有一定的參加價(jià)值,有興趣的可以了解一下。
    2017-01-01
  • 深入學(xué)習(xí)Java單元測(cè)試(Junit+Mock+代碼覆蓋率)

    深入學(xué)習(xí)Java單元測(cè)試(Junit+Mock+代碼覆蓋率)

    在做單元測(cè)試時(shí),代碼覆蓋率常常被拿來(lái)作為衡量測(cè)試好壞的指標(biāo),甚至,用代碼覆蓋率來(lái)考核測(cè)試任務(wù)完成情況,比如,代碼覆蓋率必須達(dá)到80%或 90%。下面我們就來(lái)詳細(xì)學(xué)習(xí)下java單元測(cè)試吧
    2019-06-06
  • 詳解IDEA多module項(xiàng)目maven依賴的一些說(shuō)明

    詳解IDEA多module項(xiàng)目maven依賴的一些說(shuō)明

    這篇文章主要介紹了詳解IDEA多module項(xiàng)目maven依賴的一些說(shuō)明,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-10-10
  • Java中this()與super()的用法區(qū)別解析

    Java中this()與super()的用法區(qū)別解析

    this()調(diào)用本類(lèi)構(gòu)造函數(shù)復(fù)用代碼,super()調(diào)用父類(lèi)構(gòu)造函數(shù)初始化繼承,兩者均需為構(gòu)造函數(shù)首條語(yǔ)句,適用場(chǎng)景分別為代碼復(fù)用和父類(lèi)初始化,本文給大家介紹Java中this()與super()的用法區(qū)別,感興趣的朋友跟隨小編一起看看吧
    2025-08-08
  • idea啟動(dòng)命令過(guò)長(zhǎng)的問(wèn)題及解決

    idea啟動(dòng)命令過(guò)長(zhǎng)的問(wèn)題及解決

    當(dāng)IDEA啟動(dòng)命令過(guò)長(zhǎng)時(shí),可以通過(guò)修改workspace.xml文件或調(diào)整啟動(dòng)類(lèi)配置來(lái)解決,方案一是在.idea文件或項(xiàng)目目錄中修改workspace.xml;方案二是通過(guò)運(yùn)行配置(run->edit)來(lái)保存啟動(dòng)設(shè)置,這兩種方法都可以有效縮短命令長(zhǎng)度,解決啟動(dòng)錯(cuò)誤
    2024-09-09
  • Java SPI用法案例詳解

    Java SPI用法案例詳解

    這篇文章主要介紹了Java SPI用法案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08

最新評(píng)論

景德镇市| 五华县| 龙门县| 时尚| 安阳市| 天峻县| 辽宁省| 陈巴尔虎旗| 木兰县| 云阳县| 栖霞市| 浦城县| 阿拉善盟| 高尔夫| 青岛市| 庆阳市| 锡林浩特市| 遵义县| 耒阳市| 白水县| 宁陵县| 前郭尔| 平乡县| 凤山市| 崇文区| 遂川县| 福清市| 辉南县| 象州县| 尼玛县| 澄城县| 洪泽县| 台北市| 忻城县| 府谷县| 郸城县| 吉林市| 岗巴县| 黔东| 慈利县| 新巴尔虎左旗|