原創(chuàng)文章&經(jīng)驗(yàn)總結(jié)&從校招到A廠一路陽(yáng)光一路滄桑
詳情請(qǐng)戳www.codercc.com
1. BlockingQueue簡(jiǎn)介
在實(shí)際編程中髓废,會(huì)經(jīng)常使用到JDK中Collection集合框架中的各種容器類如實(shí)現(xiàn)List,Map,Queue接口的容器類舷嗡,但是這些容器類基本上不是線程安全的弛随,除了使用Collections可以將其轉(zhuǎn)換為線程安全的容器,Doug Lea大師為我們都準(zhǔn)備了對(duì)應(yīng)的線程安全的容器纫溃,如實(shí)現(xiàn)List接口的CopyOnWriteArrayList(關(guān)于CopyOnWriteArrayList可以看這篇文章)已球,實(shí)現(xiàn)Map接口的ConcurrentHashMap(關(guān)于ConcurrentHashMap可以看這篇文章)神凑,實(shí)現(xiàn)Queue接口的ConcurrentLinkedQueue(關(guān)于ConcurrentLinkedQueue可以看這篇文章)。
最常用的"生產(chǎn)者-消費(fèi)者"問(wèn)題中竿报,隊(duì)列通常被視作線程間操作的數(shù)據(jù)容器铅乡,這樣,可以對(duì)各個(gè)模塊的業(yè)務(wù)功能進(jìn)行解耦烈菌,生產(chǎn)者將“生產(chǎn)”出來(lái)的數(shù)據(jù)放置在數(shù)據(jù)容器中阵幸,而消費(fèi)者僅僅只需要在“數(shù)據(jù)容器”中進(jìn)行獲取數(shù)據(jù)即可,這樣生產(chǎn)者線程和消費(fèi)者線程就能夠進(jìn)行解耦芽世,只專注于自己的業(yè)務(wù)功能即可挚赊。阻塞隊(duì)列(BlockingQueue)被廣泛使用在“生產(chǎn)者-消費(fèi)者”問(wèn)題中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法济瓢。當(dāng)隊(duì)列容器已滿咬腕,生產(chǎn)者線程會(huì)被阻塞,直到隊(duì)列未滿葬荷;當(dāng)隊(duì)列容器為空時(shí)涨共,消費(fèi)者線程會(huì)被阻塞,直至隊(duì)列非空時(shí)為止宠漩。
2. 基本操作
BlockingQueue基本操作總結(jié)如下(此圖來(lái)源于JAVA API文檔):
BlockingQueue繼承于Queue接口举反,因此,對(duì)數(shù)據(jù)元素的基本操作有:
插入元素
- add(E e) :往隊(duì)列插入數(shù)據(jù)扒吁,當(dāng)隊(duì)列滿時(shí)火鼻,插入元素時(shí)會(huì)拋出IllegalStateException異常;
- offer(E e):當(dāng)往隊(duì)列插入數(shù)據(jù)時(shí)雕崩,插入成功返回
true
魁索,否則則返回false
。當(dāng)隊(duì)列滿時(shí)不會(huì)拋出異常盼铁;
刪除元素
- remove(Object o):從隊(duì)列中刪除數(shù)據(jù)粗蔚,成功則返回
true
,否則為false
- poll:刪除數(shù)據(jù)饶火,當(dāng)隊(duì)列為空時(shí)鹏控,返回null致扯;
查看元素
- element:獲取隊(duì)頭元素,如果隊(duì)列為空時(shí)則拋出NoSuchElementException異常当辐;
- peek:獲取隊(duì)頭元素抖僵,如果隊(duì)列為空則拋出NoSuchElementException異常
BlockingQueue具有的特殊操作:
插入數(shù)據(jù):
- put:當(dāng)阻塞隊(duì)列容量已經(jīng)滿時(shí),往阻塞隊(duì)列插入數(shù)據(jù)的線程會(huì)被阻塞缘揪,直至阻塞隊(duì)列已經(jīng)有空余的容量可供使用耍群;
- offer(E e, long timeout, TimeUnit unit):若阻塞隊(duì)列已經(jīng)滿時(shí),同樣會(huì)阻塞插入數(shù)據(jù)的線程找筝,直至阻塞隊(duì)列已經(jīng)有空余的地方世吨,與put方法不同的是,該方法會(huì)有一個(gè)超時(shí)時(shí)間呻征,若超過(guò)當(dāng)前給定的超時(shí)時(shí)間耘婚,插入數(shù)據(jù)的線程會(huì)退出;
刪除數(shù)據(jù)
- take():當(dāng)阻塞隊(duì)列為空時(shí)陆赋,獲取隊(duì)頭數(shù)據(jù)的線程會(huì)被阻塞沐祷;
- poll(long timeout, TimeUnit unit):當(dāng)阻塞隊(duì)列為空時(shí),獲取數(shù)據(jù)的線程會(huì)被阻塞攒岛,另外赖临,如果被阻塞的線程超過(guò)了給定的時(shí)長(zhǎng),該線程會(huì)退出
3. 常用的BlockingQueue
實(shí)現(xiàn)BlockingQueue接口的有ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue
灾锯,而這幾種常見(jiàn)的阻塞隊(duì)列也是在實(shí)際編程中會(huì)常用的兢榨,下面對(duì)這幾種常見(jiàn)的阻塞隊(duì)列進(jìn)行說(shuō)明:
1.ArrayBlockingQueue
ArrayBlockingQueue是由數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列。該隊(duì)列命令元素FIFO(先進(jìn)先出)顺饮。因此吵聪,對(duì)頭元素時(shí)隊(duì)列中存在時(shí)間最長(zhǎng)的數(shù)據(jù)元素,而對(duì)尾數(shù)據(jù)則是當(dāng)前隊(duì)列最新的數(shù)據(jù)元素兼雄。ArrayBlockingQueue可作為“有界數(shù)據(jù)緩沖區(qū)”吟逝,生產(chǎn)者插入數(shù)據(jù)到隊(duì)列容器中,并由消費(fèi)者提取赦肋。ArrayBlockingQueue一旦創(chuàng)建块攒,容量不能改變。
當(dāng)隊(duì)列容量滿時(shí)佃乘,嘗試將元素放入隊(duì)列將導(dǎo)致操作阻塞;嘗試從一個(gè)空隊(duì)列中取一個(gè)元素也會(huì)同樣阻塞囱井。
ArrayBlockingQueue默認(rèn)情況下不能保證線程訪問(wèn)隊(duì)列的公平性,所謂公平性是指嚴(yán)格按照線程等待的絕對(duì)時(shí)間順序趣避,即最先等待的線程能夠最先訪問(wèn)到ArrayBlockingQueue庞呕。而非公平性則是指訪問(wèn)ArrayBlockingQueue的順序不是遵守嚴(yán)格的時(shí)間順序,有可能存在鹅巍,一旦ArrayBlockingQueue可以被訪問(wèn)時(shí)千扶,長(zhǎng)時(shí)間阻塞的線程依然無(wú)法訪問(wèn)到ArrayBlockingQueue。如果保證公平性骆捧,通常會(huì)降低吞吐量澎羞。如果需要獲得公平性的ArrayBlockingQueue,可采用如下代碼:
private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);
關(guān)于ArrayBlockingQueue的實(shí)現(xiàn)原理敛苇,可以看這篇文章妆绞。
2.LinkedBlockingQueue
LinkedBlockingQueue是用鏈表實(shí)現(xiàn)的有界阻塞隊(duì)列,同樣滿足FIFO的特性枫攀,與ArrayBlockingQueue相比起來(lái)具有更高的吞吐量括饶,為了防止LinkedBlockingQueue容量迅速增,損耗大量?jī)?nèi)存来涨。通常在創(chuàng)建LinkedBlockingQueue對(duì)象時(shí)图焰,會(huì)指定其大小,如果未指定蹦掐,容量等于Integer.MAX_VALUE
3.PriorityBlockingQueue
PriorityBlockingQueue是一個(gè)支持優(yōu)先級(jí)的無(wú)界阻塞隊(duì)列技羔。默認(rèn)情況下元素采用自然順序進(jìn)行排序,也可以通過(guò)自定義類實(shí)現(xiàn)compareTo()方法來(lái)指定元素排序規(guī)則卧抗,或者初始化時(shí)通過(guò)構(gòu)造器參數(shù)Comparator來(lái)指定排序規(guī)則藤滥。
4.SynchronousQueue
SynchronousQueue每個(gè)插入操作必須等待另一個(gè)線程進(jìn)行相應(yīng)的刪除操作,因此社裆,SynchronousQueue實(shí)際上沒(méi)有存儲(chǔ)任何數(shù)據(jù)元素拙绊,因?yàn)橹挥芯€程在刪除數(shù)據(jù)時(shí),其他線程才能插入數(shù)據(jù)泳秀,同樣的标沪,如果當(dāng)前有線程在插入數(shù)據(jù)時(shí),線程才能刪除數(shù)據(jù)嗜傅。SynchronousQueue也可以通過(guò)構(gòu)造器參數(shù)來(lái)為其指定公平性谨娜。
5.LinkedTransferQueue
LinkedTransferQueue是一個(gè)由鏈表數(shù)據(jù)結(jié)構(gòu)構(gòu)成的無(wú)界阻塞隊(duì)列,由于該隊(duì)列實(shí)現(xiàn)了TransferQueue接口磺陡,與其他阻塞隊(duì)列相比主要有以下不同的方法:
transfer(E e)
如果當(dāng)前有線程(消費(fèi)者)正在調(diào)用take()方法或者可延時(shí)的poll()方法進(jìn)行消費(fèi)數(shù)據(jù)時(shí)趴梢,生產(chǎn)者線程可以調(diào)用transfer方法將數(shù)據(jù)傳遞給消費(fèi)者線程。如果當(dāng)前沒(méi)有消費(fèi)者線程的話币他,生產(chǎn)者線程就會(huì)將數(shù)據(jù)插入到隊(duì)尾坞靶,直到有消費(fèi)者能夠進(jìn)行消費(fèi)才能退出;
tryTransfer(E e)
tryTransfer方法如果當(dāng)前有消費(fèi)者線程(調(diào)用take方法或者具有超時(shí)特性的poll方法)正在消費(fèi)數(shù)據(jù)的話蝴悉,該方法可以將數(shù)據(jù)立即傳送給消費(fèi)者線程彰阴,如果當(dāng)前沒(méi)有消費(fèi)者線程消費(fèi)數(shù)據(jù)的話,就立即返回false
拍冠。因此尿这,與transfer方法相比簇抵,transfer方法是必須等到有消費(fèi)者線程消費(fèi)數(shù)據(jù)時(shí),生產(chǎn)者線程才能夠返回射众。而tryTransfer方法能夠立即返回結(jié)果退出碟摆。
tryTransfer(E e,long timeout,imeUnit unit)</br>
與transfer基本功能一樣,只是增加了超時(shí)特性叨橱,如果數(shù)據(jù)才規(guī)定的超時(shí)時(shí)間內(nèi)沒(méi)有消費(fèi)者進(jìn)行消費(fèi)的話典蜕,就返回false
。
6.LinkedBlockingDeque
LinkedBlockingDeque是基于鏈表數(shù)據(jù)結(jié)構(gòu)的有界阻塞雙端隊(duì)列罗洗,如果在創(chuàng)建對(duì)象時(shí)為指定大小時(shí)愉舔,其默認(rèn)大小為Integer.MAX_VALUE。與LinkedBlockingQueue相比伙菜,主要的不同點(diǎn)在于轩缤,LinkedBlockingDeque具有雙端隊(duì)列的特性。LinkedBlockingDeque基本操作如下圖所示(來(lái)源于java文檔)
如上圖所示贩绕,LinkedBlockingDeque的基本操作可以分為四種類型:1.特殊情況典奉,拋出異常;2.特殊情況丧叽,返回特殊值如null或者false卫玖;3.當(dāng)線程不滿足操作條件時(shí),線程會(huì)被阻塞直至條件滿足踊淳;4. 操作具有超時(shí)特性假瞬。
另外,LinkedBlockingDeque實(shí)現(xiàn)了BlockingDueue接口而LinkedBlockingQueue實(shí)現(xiàn)的是BlockingQueue迂尝,這兩個(gè)接口的主要區(qū)別如下圖所示(來(lái)源于java文檔):
從上圖可以看出脱茉,兩個(gè)接口的功能是可以等價(jià)使用的,比如BlockingQueue的add方法和BlockingDeque的addLast方法的功能是一樣的垄开。
7.DelayQueue
DelayQueue是一個(gè)存放實(shí)現(xiàn)Delayed接口的數(shù)據(jù)的無(wú)界阻塞隊(duì)列琴许,只有當(dāng)數(shù)據(jù)對(duì)象的延時(shí)時(shí)間達(dá)到時(shí)才能插入到隊(duì)列進(jìn)行存儲(chǔ)。如果當(dāng)前所有的數(shù)據(jù)都還沒(méi)有達(dá)到創(chuàng)建時(shí)所指定的延時(shí)期溉躲,則隊(duì)列沒(méi)有隊(duì)頭榜田,并且線程通過(guò)poll等方法獲取數(shù)據(jù)元素則返回null。所謂數(shù)據(jù)延時(shí)期滿時(shí)锻梳,則是通過(guò)Delayed接口的getDelay(TimeUnit.NANOSECONDS)
來(lái)進(jìn)行判定箭券,如果該方法返回的是小于等于0則說(shuō)明該數(shù)據(jù)元素的延時(shí)期已滿。