在并發(fā)隊(duì)列上JDK提供了兩套實(shí)現(xiàn),一個(gè)是以ConcurrentLinkedQueue為代表的高性能隊(duì)
列非阻塞隊(duì)列身害,一個(gè)是以BlockingQueue接口為代表的阻塞隊(duì)列侦香,無(wú)論哪種都繼承自Queue。
阻塞隊(duì)列與非阻塞隊(duì)
阻塞隊(duì)列與普通隊(duì)列的區(qū)別在于,當(dāng)隊(duì)列是空的時(shí)蘑拯,從隊(duì)列中獲取元素的操作將會(huì)被阻塞,或者當(dāng)隊(duì)列是滿時(shí)兜粘,往隊(duì)列里添加元素的操作會(huì)被阻塞申窘。試圖從空的阻塞隊(duì)列中獲取元素的線程將會(huì)被阻塞,直到其他的線程往空的隊(duì)列插入新的元素孔轴。同樣剃法,試圖往已滿的阻塞隊(duì)列中添加新元素的線程同樣也會(huì)被阻塞,直到其他的線程使隊(duì)列重新變得空閑起來路鹰,如從隊(duì)列中移除一個(gè)或者多個(gè)元素贷洲,或者完全清空隊(duì)列.
1.ArrayDeque, (數(shù)組雙端隊(duì)列)
2.PriorityQueue, (優(yōu)先級(jí)隊(duì)列)
3.ConcurrentLinkedQueue, (基于鏈表的并發(fā)隊(duì)列)
4.DelayQueue, (延期阻塞隊(duì)列)(阻塞隊(duì)列實(shí)現(xiàn)了BlockingQueue接口)
5.ArrayBlockingQueue, (基于數(shù)組的并發(fā)阻塞隊(duì)列)
6.LinkedBlockingQueue, (基于鏈表的FIFO阻塞隊(duì)列)
7.LinkedBlockingDeque, (基于鏈表的FIFO雙端阻塞隊(duì)列)
8.PriorityBlockingQueue, (帶優(yōu)先級(jí)的無(wú)界阻塞隊(duì)列)
9.SynchronousQueue (并發(fā)同步阻塞隊(duì)列)
~ConcurrentLinkedDeque
ConcurrentLinkedQueue : 是一個(gè)適用于高并發(fā)場(chǎng)景下的隊(duì)列,通過無(wú)鎖的方式晋柱,實(shí)現(xiàn)
了高并發(fā)狀態(tài)下的高性能优构,通常ConcurrentLinkedQueue性能好于BlockingQueue.它
是一個(gè)基于鏈接節(jié)點(diǎn)的無(wú)界線程安全隊(duì)列。該隊(duì)列的元素遵循先進(jìn)先出的原則雁竞。頭是最先
加入的钦椭,尾是最近加入的,該隊(duì)列不允許null元素碑诉。
ConcurrentLinkedQueue重要方法:
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個(gè)方法沒有任何區(qū)別)
poll() 和peek() 都是取頭元素節(jié)點(diǎn)彪腔,區(qū)別在于前者會(huì)刪除元素,后者不會(huì)进栽。
ConcurrentLinkedDeque q = new ConcurrentLinkedDeque();
q.offer("余勝軍");
q.offer("碼云");
q.offer("螞蟻課堂");
q.offer("張杰");
q.offer("艾姐");
//從頭獲取元素,刪除該元素
System.out.println(q.poll());
//從頭獲取元素,不刪除該元素
System.out.println(q.peek());
//獲取總長(zhǎng)度
System.out.println(q.size());
~BlockingQueue
- 阻塞隊(duì)列(BlockingQueue)是一個(gè)支持兩個(gè)附加操作的隊(duì)列德挣。這兩個(gè)附加的操作是:
在隊(duì)列為空時(shí),獲取元素的線程會(huì)等待隊(duì)列變?yōu)榉强铡?br> 當(dāng)隊(duì)列滿時(shí)快毛,存儲(chǔ)元素的線程會(huì)等待隊(duì)列可用格嗅。
阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景,生產(chǎn)者是往隊(duì)列里添加元素的線程祸泪,消費(fèi)者是從隊(duì)列里拿元素的線程吗浩。阻塞隊(duì)列就是生產(chǎn)者存放元素的容器,而消費(fèi)者也只從容器里拿元素没隘。
BlockingQueue即阻塞隊(duì)列懂扼,從阻塞這個(gè)詞可以看出,在某些情況下對(duì)阻塞隊(duì)列的訪問可能會(huì)造成阻塞。被阻塞的情況主要有如下兩種:
- 當(dāng)隊(duì)列滿了的時(shí)候進(jìn)行入隊(duì)列操作
- 當(dāng)隊(duì)列空了的時(shí)候進(jìn)行出隊(duì)列操作
因此阀湿,當(dāng)一個(gè)線程試圖對(duì)一個(gè)已經(jīng)滿了的隊(duì)列進(jìn)行入隊(duì)列操作時(shí)赶熟,它將會(huì)被阻塞,除非有另一個(gè)線程做了出隊(duì)列操作陷嘴;同樣映砖,當(dāng)一個(gè)線程試圖對(duì)一個(gè)空隊(duì)列進(jìn)行出隊(duì)列操作時(shí),它將會(huì)被阻塞灾挨,除非有另一個(gè)線程進(jìn)行了入隊(duì)列操作邑退。
在Java中,BlockingQueue的接口位于java.util.concurrent 包中(在Java5版本開始提供)劳澄,由上面介紹的阻塞隊(duì)列的特性可知地技,阻塞隊(duì)列是線程安全的。
在新增的Concurrent包中秒拔,BlockingQueue很好的解決了多線程中莫矗,如何高效安全“傳輸”數(shù)據(jù)的問題。通過這些高效并且線程安全的隊(duì)列類砂缩,為我們快速搭建高質(zhì)量的多線程程序帶來極大的便利作谚。本文詳細(xì)介紹了BlockingQueue家庭中的所有成員,包括他們各自的功能以及常見使用場(chǎng)景庵芭。
認(rèn)識(shí)BlockingQueue
阻塞隊(duì)列妹懒,顧名思義,首先它是一個(gè)隊(duì)列喳挑,而一個(gè)隊(duì)列在數(shù)據(jù)結(jié)構(gòu)中所起的作用大致如下圖所示:從上圖我們可以很清楚看到彬伦,通過一個(gè)共享的隊(duì)列,可以使得數(shù)據(jù)由隊(duì)列的一端輸入伊诵,從另外一端輸出单绑;
常用的隊(duì)列主要有以下兩種:(當(dāng)然通過不同的實(shí)現(xiàn)方式,還可以延伸出很多不同類型的隊(duì)列曹宴,DelayQueue就是其中的一種)
- 先進(jìn)先出(FIFO):先插入的隊(duì)列的元素也最先出隊(duì)列搂橙,類似于排隊(duì)的功能。從某種程度上來說這種隊(duì)列也體現(xiàn)了一種公平性笛坦。
后進(jìn)先出(LIFO):后插入隊(duì)列的元素最先出隊(duì)列区转,這種隊(duì)列優(yōu)先處理最近發(fā)生的事件。
- 多線程環(huán)境中版扩,通過隊(duì)列可以很容易實(shí)現(xiàn)數(shù)據(jù)共享废离,比如經(jīng)典的“生產(chǎn)者”和“消費(fèi)者”模型中,通過隊(duì)列可以很便利地實(shí)現(xiàn)兩者之間的數(shù)據(jù)共享礁芦。假設(shè)我們有若干生產(chǎn)者線程蜻韭,另外又有若干個(gè)消費(fèi)者線程悼尾。如果生產(chǎn)者線程需要把準(zhǔn)備好的數(shù)據(jù)共享給消費(fèi)者線程,利用隊(duì)列的方式來傳遞數(shù)據(jù)肖方,就可以很方便地解決他們之間的數(shù)據(jù)共享問題闺魏。但如果生產(chǎn)者和消費(fèi)者在某個(gè)時(shí)間段內(nèi),萬(wàn)一發(fā)生數(shù)據(jù)處理速度不匹配的情況呢俯画?理想情況下析桥,如果生產(chǎn)者產(chǎn)出數(shù)據(jù)的速度大于消費(fèi)者消費(fèi)的速度,并且當(dāng)生產(chǎn)出來的數(shù)據(jù)累積到一定程度的時(shí)候艰垂,那么生產(chǎn)者必須暫停等待一下(阻塞生產(chǎn)者線程)泡仗,以便等待消費(fèi)者線程把累積的數(shù)據(jù)處理完畢,反之亦然材泄。然而沮焕,在concurrent包發(fā)布以前,在多線程環(huán)境下拉宗,我們每個(gè)程序員都必須去自己控制這些細(xì)節(jié),尤其還要兼顧效率和線程安全辣辫,而這會(huì)給我們的程序帶來不小的復(fù)雜度旦事。好在此時(shí),強(qiáng)大的concurrent包橫空出世了急灭,而他也給我們帶來了強(qiáng)大的BlockingQueue姐浮。(在多線程領(lǐng)域:所謂阻塞,在某些情況下會(huì)掛起線程(即阻塞)葬馋,一旦條件滿足卖鲤,被掛起的線程又會(huì)自動(dòng)被喚醒)
下面兩幅圖演示了BlockingQueue的兩個(gè)常見阻塞場(chǎng)景:
.ArrayBlockingQueue
ArrayBlockingQueue是一個(gè)有邊界的阻塞隊(duì)列,它的內(nèi)部實(shí)現(xiàn)是一個(gè)數(shù)組畴嘶。有邊界的意思是它的容量是有限的蛋逾,我們必須在其初始化的時(shí)候指定它的容量大小,容量大小一旦指定就不可改變窗悯。
ArrayBlockingQueue是以先進(jìn)先出的方式存儲(chǔ)數(shù)據(jù)区匣,最新插入的對(duì)象是尾部,最新移出的對(duì)象是頭部蒋院。下面
是一個(gè)初始化和使用ArrayBlockingQueue的例子:
<String> arrays = new ArrayBlockingQueue<String>(3);
arrays.add("李四");
arrays.add("張軍");
arrays.add("張軍");
// 添加阻塞隊(duì)列
arrays.offer("張三", 1, TimeUnit.SECONDS);
.LinkedBlockingQueue
LinkedBlockingQueue阻塞隊(duì)列大小的配置是可選的亏钩,如果我們初始化時(shí)指定一個(gè)大小,它就是有邊界的欺旧,如果不指定姑丑,它就是無(wú)邊界的。說是無(wú)邊界辞友,其實(shí)是采用了默認(rèn)大小為Integer.MAX_VALUE的容量 栅哀。它的內(nèi)部實(shí)現(xiàn)是一個(gè)鏈表。
和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進(jìn)先出的方式存儲(chǔ)數(shù)據(jù)昌屉,最新插入的對(duì)象是尾部钙蒙,最新移出的對(duì)象是頭部。下面是一個(gè)初始化和使LinkedBlockingQueue的例子:
LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(3);
linkedBlockingQueue.add("張三");
linkedBlockingQueue.add("李四");
linkedBlockingQueue.add("李四");
System.out.println(linkedBlockingQueue.size());
.PriorityBlockingQueue
PriorityBlockingQueue是一個(gè)沒有邊界的隊(duì)列间驮,它的排序規(guī)則和 java.util.PriorityQueue一樣躬厌。需要注
意,PriorityBlockingQueue中允許插入null對(duì)象竞帽。
所有插入PriorityBlockingQueue的對(duì)象必須實(shí)現(xiàn) java.lang.Comparable接口扛施,隊(duì)列優(yōu)先級(jí)的排序規(guī)則就
是按照我們對(duì)這個(gè)接口的實(shí)現(xiàn)來定義的。
另外屹篓,我們可以從PriorityBlockingQueue獲得一個(gè)迭代器Iterator疙渣,但這個(gè)迭代器并不保證按照優(yōu)先級(jí)順
序進(jìn)行迭代。
下面我們舉個(gè)例子來說明一下堆巧,首先我們定義一個(gè)對(duì)象類型妄荔,這個(gè)對(duì)象需要實(shí)現(xiàn)Comparable接口:
.SynchronousQueue
SynchronousQueue隊(duì)列內(nèi)部?jī)H允許容納一個(gè)元素。當(dāng)一個(gè)線程插入一個(gè)元素后會(huì)被阻塞谍肤,除非這個(gè)元素被另一個(gè)線程消費(fèi)啦租。