juc系列-并發(fā)Queue

ConcurrentLinkedQueue是一個(gè)基于鏈表結(jié)構(gòu)的無(wú)界隊(duì)列,提供了Queue的基本特性FIFO,出入規(guī)則是:從head出魁衙,從tail進(jìn)报腔。非阻塞特性使其在高并發(fā)環(huán)境依然能有出色的性能。

ConcurrentLinkedQueue的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu):

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;

    public ConcurrentLinkedQueue() {
        head = tail = new Node<E>(null);
    }
    
    剖淀。纯蛾。。
    
    private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
        纵隔。翻诉。。
    }

下面是ConcurrentLinkedQueue的一些需要注意的點(diǎn):

1.不允許存儲(chǔ)null值,否則拋出NPE

2.Iterator遍歷時(shí)數(shù)據(jù)的弱一致性

3.size方法沒(méi)有絕對(duì)的實(shí)時(shí)性

4.沒(méi)有fast-fail機(jī)制捌刮,不會(huì)拋出ConcurrentModificationException

5.head,tail節(jié)點(diǎn)的延遲更新處理

6.無(wú)鎖化設(shè)計(jì)的ABA問(wèn)題

1.不允許存儲(chǔ)null值,否則拋出NPE

每次offer操作時(shí)都需要進(jìn)行checkNotNull操作,若item為null,直接跑NPE.

private static void checkNotNull(Object v) {
    if (v == null)
        throw new NullPointerException();
}
2.Iterator遍歷時(shí)數(shù)據(jù)的弱一致性 3.size方法沒(méi)有絕對(duì)的實(shí)時(shí)性 4.沒(méi)有fast-fail機(jī)制

前兩個(gè)問(wèn)題都是由于ConcurrentLinkedQueue的異步特性造成的,在遍歷時(shí)無(wú)法保證隊(duì)列不會(huì)被其他線程修改.并且ConcurrentLinkedQueue沒(méi)有fast-fail機(jī)制碰煌,即在遍歷隊(duì)列時(shí),隊(duì)列被別的線程修改并不會(huì)拋出ConcurrentModificationException绅作。所以當(dāng)前線程對(duì)于其他線程做的修改不能及時(shí)感知到芦圾。除了Iterator遍歷/size()方法,其他所有批量操作/全局操作的方法都存在這個(gè)問(wèn)題(如:toArray/addAll等)

5.head,tail節(jié)點(diǎn)的延遲更新處理

這點(diǎn)是最初接觸ConcurrentLinkedQueue時(shí)比較困惑的地方棚蓄,head和tail節(jié)點(diǎn)并不是實(shí)時(shí)更新的堕扶,也就是說(shuō)每次offer/poll操作并不一定改變head/tail的值碍脏,這種處理方式可以減少入隊(duì)出隊(duì)時(shí)的cas操作梭依,對(duì)性能是個(gè)很大的提升。下面結(jié)合offer和poll操作來(lái)具體分析下:

執(zhí)行操作:初始化

ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();

隊(duì)列狀態(tài)

concurrentlinkedqueue_init.png

執(zhí)行操作:queue.offer("aaa");

隊(duì)列狀態(tài)

concurrentlinkedqueue_offer_aaa.png

執(zhí)行操作:queue.offer("bbb");

隊(duì)列狀態(tài)

offer_bbb.png

執(zhí)行操作:queue.offer("ccc");

隊(duì)列狀態(tài)

offer_ccc.png

執(zhí)行操作:queue.offer("ddd");

隊(duì)列狀態(tài)

offer_ddd.png

下面根據(jù)offer源碼具體分析:

offer操作源碼:

    public boolean offer(E e) {
        checkNotNull(e);//NPE檢查
        final Node<E> newNode = new Node<E>(e);

        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;                                      
(1)         if (q == null) {
                // p是最后一個(gè)節(jié)點(diǎn),cas更新
                if (p.casNext(null, newNode)) {
                    //cas更新成功
                    if (p != t)
                        casTail(t, newNode);  //cas更新tail
                    return true;
                }
                // cas操作失敗,重復(fù)操作
            }
(2)        else if (p == q)
                p = (t != (t = tail)) ? t : head;
(3)         else
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

offer操作可以分為兩個(gè)步驟:1.確定尾節(jié)點(diǎn) 2. 做cas更新

從上面的狀態(tài)圖中可以看到典尾,tail指向的節(jié)點(diǎn)并不一定是隊(duì)列的尾節(jié)點(diǎn)役拴,所以要做元素入隊(duì)操作的第一要?jiǎng)?wù)是確定尾節(jié)點(diǎn).

重點(diǎn)分析下為什么q 會(huì)出現(xiàn)以上三種情況:

  1. q == null

當(dāng)前的隊(duì)列狀態(tài)1

offer_bbb.png

執(zhí)行操作:queue.offer("ccc");

當(dāng)tail指向隊(duì)列的最后一個(gè)節(jié)點(diǎn)bbb的時(shí)候,tail.next為null钾埂,此時(shí)q 為 null,執(zhí)行cas更新節(jié)點(diǎn)的操作河闰,p.casNext(null, newNode),將p的next值設(shè)置為newNode褥紫,等同于p.next = newNode;
隊(duì)列的狀態(tài)2如下:

offer_ccc.png

問(wèn)題:為什么需要做下面這個(gè)判斷姜性?這個(gè)cas操作失敗了會(huì)有什么后果?

if (p != t)
    casTail(t, newNode); 

下面繼續(xù)分析

在上面隊(duì)列狀態(tài)基礎(chǔ)上執(zhí)行操作:queue.offer("ddd");

此時(shí) p = tail,q = p.next; p指向bbb髓考,q指向ccc部念。狀態(tài)如下:

offerpq.png

這種情況下 q != null 且 p != q,所以執(zhí)行(3)操作:

p = (p != t && t != (t = tail)) ? t : q;

這步操作后 p 指向了 節(jié)點(diǎn)ccc。即p指向了尾節(jié)點(diǎn)。尾節(jié)點(diǎn)確定了儡炼,那么下次循環(huán)即可做cas更新操作了妓湘。

得到最新?tīng)顟B(tài)3:


offer_ddd.png

在隊(duì)列狀態(tài)2【即執(zhí)行queue.offer("ccc")后】的基礎(chǔ)上做如下操作:

在并發(fā)環(huán)境中,當(dāng)線程A執(zhí)行操作queue.offer("ddd");之前乌询,同時(shí)另一個(gè)線程執(zhí)行完以下三次poll操作:

queue.poll();
queue.poll();
queue.poll();

此時(shí)線程A看到的隊(duì)列的狀態(tài)如下:


offer_poll.png

所以當(dāng) p == q時(shí)榜贴,表示當(dāng)前節(jié)點(diǎn)已經(jīng)不再鏈表中(已被移除)。所以這種情況下需要將p重新指向head或tail妹田。

p = (t != (t = tail)) ? t : head;

至此唬党,offer操作中的三種情況分析完畢。

下面分析poll操作秆麸。

當(dāng)前狀態(tài)

offer_ddd.png

執(zhí)行操作:queue.poll();
狀態(tài)如下:


poll1.png

繼續(xù)執(zhí)行操作:queue.poll();
狀態(tài)如下:


poll2.png

繼續(xù)執(zhí)行操作:queue.poll(); 狀態(tài)如下

poll3.png

可見(jiàn)head的更新和tail類似初嘹,都是采用了延遲更新的優(yōu)化方式。
poll源碼如下:

    public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;

                if (item != null && p.casItem(item, null)) {
                    // 成功將item置為null
                    if (p != h) // 更新head
                        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)
                    continue restartFromHead;
                else
                    p = q;
            }
        }
    }

poll操作和offer類似沮趣,先獲取head屯烦,然后cas(依情況決定是否更新head),一般hop為2房铭。

6.無(wú)鎖化設(shè)計(jì)的ABA問(wèn)題

在用cas做lock-free操作時(shí)有一個(gè)經(jīng)典的ABA問(wèn)題驻龟。那么ConcurrentLinkedQueue需要考慮這個(gè)問(wèn)題嗎?

ConcurrentLinkedQueue中不存在ABA問(wèn)題缸匪,這主要依賴于Java語(yǔ)言的垃圾回收機(jī)制翁狐。當(dāng)一個(gè)節(jié)點(diǎn)被poll或remove后,即被gc凌蔬,該節(jié)點(diǎn)會(huì)被垃圾回收器回收露懒。

使用場(chǎng)景
  1. Tomcat存儲(chǔ)等待請(qǐng)求
protected ConcurrentLinkedQueue<SocketWrapper<Socket>> waitingRequests =
        new ConcurrentLinkedQueue<SocketWrapper<Socket>>();

ConcurrentLinkedDeque

JDK還提供了一個(gè)雙端隊(duì)列的實(shí)現(xiàn)版本。并發(fā)操作特性和ConcurrentLinkedQueue相似砂心,提供了Deque接口特征懈词。

參考: jdk1.7 java.util.concurrent.ConcurrentLinkedQueue源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市辩诞,隨后出現(xiàn)的幾起案子坎弯,更是在濱河造成了極大的恐慌,老刑警劉巖译暂,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抠忘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡外永,警方通過(guò)查閱死者的電腦和手機(jī)崎脉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)伯顶,“玉大人囚灼,你說(shuō)我怎么就攤上這事呛踊。” “怎么了啦撮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵谭网,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我赃春,道長(zhǎng)愉择,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任织中,我火速辦了婚禮锥涕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狭吼。我一直安慰自己层坠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布刁笙。 她就那樣靜靜地躺著破花,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疲吸。 梳的紋絲不亂的頭發(fā)上座每,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音摘悴,去河邊找鬼峭梳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蹂喻,可吹牛的內(nèi)容都是我干的葱椭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼口四,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼孵运!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起窃祝,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掐松,失蹤者是張志新(化名)和其女友劉穎踱侣,沒(méi)想到半個(gè)月后粪小,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抡句,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年探膊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片待榔。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逞壁,死狀恐怖流济,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腌闯,我是刑警寧澤绳瘟,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站姿骏,受9級(jí)特大地震影響糖声,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜分瘦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一蘸泻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘲玫,春花似錦悦施、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至土陪,卻和暖如春沐绒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旺坠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工乔遮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人取刃。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓蹋肮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親璧疗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坯辩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容