【并發(fā)編程系列8】阻塞隊(duì)列之ArrayBlockingQueue,LinkedBlockingQueue,LinkedBlockingDeque原理分析

什么是阻塞隊(duì)列

阻塞隊(duì)列有兩個(gè)特點(diǎn):

  • 當(dāng)隊(duì)列中沒(méi)有元素時(shí),從隊(duì)列中獲取元素會(huì)被阻塞
  • 當(dāng)隊(duì)列滿了時(shí)衔彻,添加元素會(huì)被阻塞

阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景薇宠,生產(chǎn)者是向隊(duì)列里添加元素,消費(fèi)者則從隊(duì)列里取元素艰额。

隊(duì)列Queue接口核心方法

阻塞隊(duì)列澄港,本質(zhì)上來(lái)說(shuō)還是屬于隊(duì)列,也就是說(shuō)阻塞隊(duì)列繼承了隊(duì)列的功能柄沮,這里我們先來(lái)看看Queue接口中的幾個(gè)核心方法:

方法 功能
add(e) 添加一個(gè)元素回梧,成功返回true,如果空間滿了則拋出異常
offer(e) 添加一個(gè)元素祖搓,成功返回true狱意,如果空間滿了則返回false,處理有界隊(duì)列時(shí)優(yōu)于add方法
remove() 檢索并移除隊(duì)列頭元素,成功則返回移除的元素拯欧,如果隊(duì)列為空則拋出異常
poll() 檢索并移除隊(duì)列頭元素详囤,成功則返回移除的元素,如果隊(duì)列為空則返回null
element() 檢索并返回隊(duì)列頭元素镐作,如果隊(duì)列為空則拋出異常
peek() 檢索并返回隊(duì)列頭元素藏姐,如果位列為空則返回null

這幾個(gè)方法是隊(duì)列接口所提供的蚓再,然而這些方法并不會(huì)阻塞,所以需要重新定義阻塞隊(duì)列的接口包各,下面我們看看阻塞隊(duì)列中的核心方法摘仅。

阻塞隊(duì)列BlockigQueue接口核心方法

方法 功能
put(e) 添加一個(gè)元素,成功返回true问畅,如果空間滿了則阻塞等待
offer(e,time,unit) 添加一個(gè)元素娃属,成功返回true,如果空間滿了則阻塞指定時(shí)間护姆,到達(dá)指定時(shí)間還沒(méi)空間則返回null
take() 檢索并移除隊(duì)列頭元素矾端,成功則返回移除的元素,如果隊(duì)列為空則阻塞
poll(time,unit) 檢索并移除隊(duì)列頭元素卵皂,成功則返回移除的元素秩铆,如果隊(duì)列為空則阻塞指定時(shí)間,到達(dá)指定時(shí)間后隊(duì)列還是空則返回null
drainTo(Collection) 一次性獲取隊(duì)列所有元素放到指定的集合中灯变,并返回轉(zhuǎn)移個(gè)數(shù)
drainTo(c,n) 一次性獲取隊(duì)列中指定個(gè)數(shù)的元素放到指定的集合中殴玛,并返回轉(zhuǎn)移個(gè)數(shù)
remainingCapacity() 返回隊(duì)列中理想情況下可添加元素個(gè)數(shù)

在Java中,提供了7種常用的阻塞隊(duì)列添祸。

  • ArrayBlockingQueue:由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列
  • LinkedBlockingQueue:由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列
  • PriorityBlockingQueue:支持優(yōu)先級(jí)排序的無(wú)界阻塞隊(duì)列
  • DelayQueue:使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無(wú)界阻塞隊(duì)列
  • SynchronousQueue:不存儲(chǔ)元素的阻塞隊(duì)列
  • LinkedTransferQueue:由鏈表結(jié)構(gòu)組成的無(wú)界阻塞隊(duì)列
  • LinkedBlockingDeque:由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列

ArrayBlockingQueue

ArrayBlockingQueue是一個(gè)用數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列滚粟。此隊(duì)列按照先進(jìn)先出(FIFO)的原則對(duì)元素進(jìn)行排序。默認(rèn)情況下采用非公平鎖的方式實(shí)現(xiàn)刃泌,可以通過(guò)構(gòu)造器傳參控制是采用公平鎖還是非公平鎖實(shí)現(xiàn)凡壤。先看看ArrayBlockingQueue類圖關(guān)系:


在這里插入圖片描述

可以看到有3個(gè)構(gòu)造器,其實(shí)最終都是會(huì)調(diào)用上圖中第二個(gè)構(gòu)造器進(jìn)行初始化,第三個(gè)構(gòu)造器在初始化之后會(huì)再進(jìn)行賦值(如果傳入的Collection不為空)耙替。

ArrayBlockingQueue nonFairQueue = new ArrayBlockingQueue(10);//默認(rèn)非公平鎖實(shí)現(xiàn)
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(10,true);//true表示公平鎖

模擬實(shí)現(xiàn)生產(chǎn)者消費(fèi)者

package com.zwx.concurrent.queue.block;

import java.util.concurrent.ArrayBlockingQueue;

public class ArrayBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue(100);//默認(rèn)非公平鎖實(shí)現(xiàn)
        new Thread(new ConsumerThread(queue)).start();
        Thread.sleep(2000);
        new Thread(new ProcuctThread(queue)).start();
    }
}
    class ProcuctThread extends Thread{
        private ArrayBlockingQueue queue;
        public ProcuctThread(ArrayBlockingQueue queue) {
            this.queue = queue;
        }
        @Override
        public void run() {
           for (int i=0;i<100;i++){
               try {
                   queue.put(i);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        }
    }

    class ConsumerThread extends Thread{
        private ArrayBlockingQueue queue;
        public ConsumerThread(ArrayBlockingQueue queue) {
            this.queue = queue;
        }
        @Override
        public void run() {
            while (true){
                try {
                    int a = (int)queue.take();
                    System.out.println("消費(fèi):" + a);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

上面的示例中亚侠,我們先啟動(dòng)了消費(fèi)者模式,運(yùn)行之后可以發(fā)現(xiàn)俗扇,因?yàn)殛?duì)列是空的硝烂,所以會(huì)阻塞等待生產(chǎn)者添加元素之后輸出。

初始化隊(duì)列

首先是初始化兩個(gè)Condition隊(duì)列分別用于阻塞生產(chǎn)者和消費(fèi)者線程狐援,關(guān)于Condition隊(duì)列钢坦,想詳細(xì)了解的可以點(diǎn)擊這里

在這里插入圖片描述

添加元素(生產(chǎn)者)

添加元素的時(shí)候啥酱,如果隊(duì)列滿了爹凹,就阻塞,不滿則調(diào)用enque方法添加元素


在這里插入圖片描述

獲取元素時(shí)通過(guò)內(nèi)部維護(hù)的putIndex逐個(gè)往后添加元素镶殷,滿了則重新從0開(kāi)始


在這里插入圖片描述

獲取元素(消費(fèi)者)

首先會(huì)判斷隊(duì)列是不是空了禾酱,空了就阻塞,否則調(diào)用dequeue方法獲取元素


在這里插入圖片描述

調(diào)用dequeue方法移除元素,并喚醒添加元素線程颤陶。


在這里插入圖片描述

LinkedBlockingQueue

一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列,此隊(duì)列按照先進(jìn)先出(FIFO)的原則對(duì)元素進(jìn)行排序颗管,和ArrayBlockingQueue的區(qū)別是ArrayBlockingQueue內(nèi)部維護(hù)的是一個(gè)數(shù)組,通過(guò)數(shù)組下標(biāo)來(lái)維護(hù)隊(duì)列滓走,而LinkedBlockingQueue維護(hù)的是一個(gè)鏈表垦江,通過(guò)Node來(lái)維護(hù)隊(duì)列。還是先來(lái)看看類圖關(guān)系:


在這里插入圖片描述

LinkedBlockingQueue依然有3個(gè)構(gòu)造函數(shù)搅方,第一個(gè)和第三個(gè)構(gòu)造器最終也是會(huì)調(diào)用第二個(gè)構(gòu)造器比吭,而且默認(rèn)會(huì)初始化一個(gè)Integer.MAX_VALUE大小隊(duì)列,第三個(gè)構(gòu)造器在初始化隊(duì)列之后會(huì)進(jìn)行賦值(如果傳入的Collection不為空)姨涡。

初始化隊(duì)列

在這里插入圖片描述

Node是LinkedBlockingQueue中的一個(gè)靜態(tài)內(nèi)部類:


在這里插入圖片描述

所以第一次初始化之后Node節(jié)點(diǎn)中的item是默認(rèn)為null值的衩藤。初始化之后得到如下隊(duì)列:


在這里插入圖片描述

這個(gè)頭節(jié)點(diǎn)也是一個(gè)哨兵,和AQS同步隊(duì)列一樣涛漂,設(shè)置了一個(gè)空信息的節(jié)點(diǎn)作為哨兵赏表。

添加元素(生產(chǎn)者)

在這里插入圖片描述

添加元素獲取的是putLock,后面可以看到匈仗,獲取元素獲取的的takeLock瓢剿,采用了讀寫雙鎖分離方式實(shí)現(xiàn)性能上的提升。
再看下添加元素的enqueue方法:


在這里插入圖片描述

添加元素后得到如下隊(duì)列:


在這里插入圖片描述

獲取元素(消費(fèi)者)

在這里插入圖片描述

這里消費(fèi)者獲取的是另一把鎖takeLock锚沸,這里的邏輯也應(yīng)該是比較容易看懂跋选,我們進(jìn)入真正獲取元素的dequeue方法:


在這里插入圖片描述

1、我們先看219行哗蜈,執(zhí)行之后得到如下隊(duì)列:


在這里插入圖片描述

2、繼續(xù)看221行代碼坠韩,執(zhí)行之后得到如下結(jié)果:


在這里插入圖片描述

經(jīng)過(guò)這兩步距潘,其實(shí)原先的E1節(jié)點(diǎn)已經(jīng)被移除了,那么從上圖可以看到只搁,原先舊的head節(jié)點(diǎn)中next還持有了當(dāng)前head節(jié)點(diǎn)的引用音比,這時(shí)候根據(jù)GC的可達(dá)性算法,是無(wú)法回收的氢惋,所以需要將next的引用去掉(也就是上面218行代碼的作用)洞翩,這樣Node節(jié)點(diǎn)就沒(méi)有持有其他對(duì)象的引用了,GC就可以將其當(dāng)做垃圾回收掉焰望,這個(gè)和之前在AQS同步隊(duì)列以及Condition隊(duì)列中做法是一個(gè)意思骚亿,都是為了取消其引用,方便GC熊赖。

LinkedBlockingDeque

LinkedBlockingDeque是和LinkedBlockingQeque一樣均是由鏈表結(jié)構(gòu)組成来屠,也就是說(shuō)內(nèi)部都是通過(guò)一個(gè)Node內(nèi)部類來(lái)實(shí)現(xiàn)聊表,而LinkedBlockingDeque是雙向的阻塞隊(duì)列,故而Node中肯定會(huì)比單向的多了一個(gè)prev指向前一個(gè)節(jié)點(diǎn)俱笛。

雙向隊(duì)列因?yàn)槎嗔艘粋€(gè)操作隊(duì)列的入口捆姜,所以相比較于LinkedBlockingQeque單向隊(duì)列中多了addFirst、 addLast迎膜、offerFirst泥技、offerLast、peekFirst和peekLast等方法磕仅。另外零抬,插入方法add等同于addLast,移除方法remove等效于removeFirst宽涌,而take方法卻等同于takeFirst平夜,這些事實(shí)需要注意的,為了避免混亂卸亮,建議使用的時(shí)候還是帶上First和Last關(guān)鍵字忽妒。
首先還是來(lái)看一看類圖:


在這里插入圖片描述

可以看出相比較于單項(xiàng)隊(duì)列,多了一個(gè)Deque(雙向)接口兼贸,構(gòu)造器和單向列表一樣段直,也是提供了3個(gè)。

初始化隊(duì)列

在這里插入圖片描述

這里可以看到溶诞,初始化的時(shí)候沒(méi)有設(shè)置任何節(jié)點(diǎn)鸯檬,僅僅只是設(shè)置了一個(gè)容量。

添加元素(生產(chǎn)者)

從First添加

在這里插入圖片描述

這里基本沒(méi)有邏輯螺垢,主要看linkFirst(Node)方法:


在這里插入圖片描述

從Last添加

在這里插入圖片描述

這個(gè)方法也是一樣沒(méi)有邏輯喧务,主要看linkLast(Node)方法:


在這里插入圖片描述

獲取元素(消費(fèi)者)

從First獲取

在這里插入圖片描述

繼續(xù)看unlinkFirst這個(gè)方法:


在這里插入圖片描述

這些邏輯和上面的單向隊(duì)列是差不多的,只是多了一個(gè)prev指向

從Last獲取

在這里插入圖片描述

繼續(xù)看unlinkLast()方法:


在這里插入圖片描述

總結(jié)

本文主要講述了Java提供的7種隊(duì)列中的其中三種隊(duì)列枉圃,這三種隊(duì)列實(shí)現(xiàn)方式比較接近功茴,而且源碼比較容易理解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末孽亲,一起剝皮案震驚了整個(gè)濱河市坎穿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌返劲,老刑警劉巖玲昧,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異篮绿,居然都是意外死亡孵延,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門搔耕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)隙袁,“玉大人痰娱,你說(shuō)我怎么就攤上這事∑惺眨” “怎么了梨睁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)娜饵。 經(jīng)常有香客問(wèn)我坡贺,道長(zhǎng),這世上最難降的妖魔是什么箱舞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任遍坟,我火速辦了婚禮,結(jié)果婚禮上晴股,老公的妹妹穿的比我還像新娘愿伴。我一直安慰自己,他們只是感情好电湘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布隔节。 她就那樣靜靜地躺著,像睡著了一般寂呛。 火紅的嫁衣襯著肌膚如雪怎诫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天贷痪,我揣著相機(jī)與錄音幻妓,去河邊找鬼。 笑死劫拢,一個(gè)胖子當(dāng)著我的面吹牛肉津,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尚镰,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼阀圾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了狗唉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涡真,失蹤者是張志新(化名)和其女友劉穎分俯,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體哆料,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缸剪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了东亦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杏节。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窥浪,到底是詐尸還是另有隱情物臂,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布嫉鲸,位于F島的核電站撑蒜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏玄渗。R本人自食惡果不足惜座菠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藤树。 院中可真熱鬧浴滴,春花似錦、人聲如沸岁钓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)甜紫。三九已至降宅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間囚霸,已是汗流浹背腰根。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拓型,地道東北人额嘿。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像劣挫,于是被迫代替她去往敵國(guó)和親册养。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348