1. java.util.concurrent – Java 并發(fā)工具包
Java 5 添加了一個新的包到 Java 平臺绽昏,java.util.concurrent 包备埃。這個包包含有一系列能夠讓 Java 的并發(fā)編程變得更加簡單輕松的類税迷。在這個包被添加以前,你需要自己去動手實(shí)現(xiàn)自己的相關(guān)工具類伞矩。
本文我將帶你一一認(rèn)識 java.util.concurrent 包里的這些類赫编,然后你可以嘗試著如何在項(xiàng)目中使用它們蛤奢。本文中我將使用 Java 6 版本,我不確定這和 Java 5 版本里的是否有一些差異饵骨。我不會去解釋關(guān)于 Java 并發(fā)的核心問題 – 其背后的原理翘悉,也就是說,如果你對那些東西感興趣居触,參考《Java 并發(fā)指南》妖混。
半成品
本文很大程度上還是個 “半成品”,所以當(dāng)你發(fā)現(xiàn)一些被漏掉的類或接口時轮洋,請耐心等待制市。在我空閑的時候會把它們加進(jìn)來的。
2. 阻塞隊(duì)列 BlockingQueue
java.util.concurrent 包里的 BlockingQueue 接口表示一個線程安放入和提取實(shí)例的隊(duì)列弊予。本小節(jié)我將給你演示如何使用這個 BlockingQueue祥楣。本節(jié)不會討論如何在 Java 中實(shí)現(xiàn)一個你自己的 BlockingQueue。如果你對那個感興趣汉柒,參考《Java 并發(fā)指南》
BlockingQueue 用法
BlockingQueue 通常用于一個線程生產(chǎn)對象误褪,而另外一個線程消費(fèi)這些對象的場景。下圖是對這個原理的闡述:
一個線程往里邊放碾褂,另外一個線程從里邊取的一個 BlockingQueue兽间。
一個線程將會持續(xù)生產(chǎn)新對象并將其插入到隊(duì)列之中,直到隊(duì)列達(dá)到它所能容納的臨界點(diǎn)正塌。也就是說嘀略,它是有限的。如果該阻塞隊(duì)列到達(dá)了其臨界點(diǎn)传货,負(fù)責(zé)生產(chǎn)的線程將會在往里邊插入新對象時發(fā)生阻塞屎鳍。它會一直處于阻塞之中,直到負(fù)責(zé)消費(fèi)的線程從隊(duì)列中拿走一個對象问裕。負(fù)責(zé)消費(fèi)的線程將會一直從該阻塞隊(duì)列中拿出對象逮壁。如果消費(fèi)線程嘗試去從一個空的隊(duì)列中提取對象的話,這個消費(fèi)線程將會處于阻塞之中粮宛,直到一個生產(chǎn)線程把一個對象丟進(jìn)隊(duì)列窥淆。
BlockingQueue 的方法
BlockingQueue 具有 4 組不同的方法用于插入卖宠、移除以及對隊(duì)列中的元素進(jìn)行檢查。如果請求的操作不能得到立即執(zhí)行的話忧饭,每個方法的表現(xiàn)也不同扛伍。這些方法如下:
四組不同的行為方式解釋:
拋異常:如果試圖的操作無法立即執(zhí)行,拋一個異常词裤。
特定值:如果試圖的操作無法立即執(zhí)行刺洒,返回一個特定的值(常常是 true / false)。
阻塞:如果試圖的操作無法立即執(zhí)行吼砂,該方法調(diào)用將會發(fā)生阻塞逆航,直到能夠執(zhí)行。
超時:如果試圖的操作無法立即執(zhí)行渔肩,該方法調(diào)用將會發(fā)生阻塞因俐,直到能夠執(zhí)行,但等待時間不會超過給定值周偎。返回一個特定值以告知該操作是否成功(典型的是 true / false)抹剩。
無法向一個 BlockingQueue 中插入 null。如果你試圖插入 null蓉坎,BlockingQueue 將會拋出一個 NullPointerException澳眷。
可以訪問到 BlockingQueue 中的所有元素,而不僅僅是開始和結(jié)束的元素蛉艾。比如說境蔼,你將一個對象放入隊(duì)列之中以等待處理,但你的應(yīng)用想要將其取消掉伺通。那么你可以調(diào)用諸如 remove(o) 方法來將隊(duì)列之中的特定對象進(jìn)行移除箍土。但是這么干效率并不高(譯者注:基于隊(duì)列的數(shù)據(jù)結(jié)構(gòu),獲取除開始或結(jié)束位置的其他對象的效率不會太高)罐监,因此你盡量不要用這一類的方法吴藻,除非你確實(shí)不得不那么做。
BlockingQueue 的實(shí)現(xiàn)
BlockingQueue 是個接口弓柱,你需要使用它的實(shí)現(xiàn)之一來使用 BlockingQueue沟堡。java.util.concurrent 具有以下 BlockingQueue 接口的實(shí)現(xiàn)(Java 6):
Java 中使用 BlockingQueue 的例子
這里是一個 Java 中使用 BlockingQueue 的示例。本示例使用的是 BlockingQueue 接口的 ArrayBlockingQueue 實(shí)現(xiàn)矢空。
首先航罗,BlockingQueueExample 類分別在兩個獨(dú)立的線程中啟動了一個 Producer 和 一個 Consumer。
Producer 向一個共享的 BlockingQueue 中注入字符串屁药,而 Consumer 則會從中把它們拿出來粥血。
以下是 Producer 類。注意它在每次 put() 調(diào)用時是如何休眠一秒鐘的。這將導(dǎo)致 Consumer 在等待隊(duì)列中對象的時候發(fā)生阻塞复亏。
以下是 Consumer 類趾娃。它只是把對象從隊(duì)列中抽取出來,然后將它們打印到 System.out缔御。
3. 數(shù)組阻塞隊(duì)列 ArrayBlockingQueue
ArrayBlockingQueue 類實(shí)現(xiàn)了 BlockingQueue 接口抬闷。
ArrayBlockingQueue 是一個有界的阻塞隊(duì)列,其內(nèi)部實(shí)現(xiàn)是將對象放到一個數(shù)組里耕突。有界也就意味著笤成,它不能夠存儲無限多數(shù)量的元素。它有一個同一時間能夠存儲元素?cái)?shù)量的上限眷茁。你可以在對其初始化的時候設(shè)定這個上限疹启,但之后就無法對這個上限進(jìn)行修改了(譯者注:因?yàn)樗腔跀?shù)組實(shí)現(xiàn)的,也就具有數(shù)組的特性:一旦初始化蔼卡,大小就無法修改)。
‘ArrayBlockingQueue 內(nèi)部以 FIFO(先進(jìn)先出)的順序?qū)υ剡M(jìn)行存儲挣磨。隊(duì)列中的頭元素在所有元素之中是放入時間最久的那個雇逞,而尾元素則是最短的那個。
以下是在使用 ArrayBlockingQueue 的時候?qū)ζ涑跏蓟囊粋€示例:
4. 延遲隊(duì)列 DelayQueue
DelayQueue 實(shí)現(xiàn)了 BlockingQueue 接口茁裙。DelayQueue 對元素進(jìn)行持有直到一個特定的延遲到期塘砸。注入其中的元素必須實(shí)現(xiàn) java.util.concurrent.Delayed 接口,該接口定義:
DelayedElement 是我所創(chuàng)建的一個 DelayedElement 接口的實(shí)現(xiàn)類晤锥,它不在 Java.util.concurrent 包里掉蔬。你需要自行創(chuàng)建你自己的 Delayed 接口的實(shí)現(xiàn)以使用 DelayQueue 類。
5. 鏈阻塞隊(duì)列 LinkedBlockingQueue
LinkedBlockingQueue 類實(shí)現(xiàn)了 BlockingQueue 接口矾瘾。
LinkedBlockingQueue 內(nèi)部以一個鏈?zhǔn)浇Y(jié)構(gòu)(鏈接節(jié)點(diǎn))對其元素進(jìn)行存儲女轿。如果需要的話,這一鏈?zhǔn)浇Y(jié)構(gòu)可以選擇一個上限壕翩。如果沒有定義上限蛉迹,將使用 Integer.MAX_VALUE 作為上限。
LinkedBlockingQueue 內(nèi)部以 FIFO(先進(jìn)先出)的順序?qū)υ剡M(jìn)行存儲放妈。隊(duì)列中的頭元素在所有元素之中是放入時間最久的那個北救,而尾元素則是最短的那個。
以下是 LinkedBlockingQueue 的初始化和使用示例代碼:
6. 具有優(yōu)先級的阻塞隊(duì)列 PriorityBlockingQueue
PriorityBlockingQueue 類實(shí)現(xiàn)了 BlockingQueue 接口芜抒。
PriorityBlockingQueue 是一個無界的并發(fā)隊(duì)列珍策。它使用了和類 java.util.PriorityQueue 一樣的排序規(guī)則。你無法向這個隊(duì)列中插入 null 值宅倒。所有插入到 PriorityBlockingQueue 的元素必須實(shí)現(xiàn) java.lang.Comparable 接口攘宙。因此該隊(duì)列中元素的排序就取決于你自己的 Comparable 實(shí)現(xiàn)。注意 PriorityBlockingQueue 對于具有相等優(yōu)先級(compare() == 0)的元素并不強(qiáng)制任何特定行為。
同時注意模聋,如果你從一個 PriorityBlockingQueue 獲得一個 Iterator 的話肩民,該 Iterator 并不能保證它對元素的遍歷是以優(yōu)先級為序的。
以下是使用 PriorityBlockingQueue 的示例:
7. 同步隊(duì)列 SynchronousQueue
SynchronousQueue 類實(shí)現(xiàn)了 BlockingQueue 接口链方。
SynchronousQueue 是一個特殊的隊(duì)列持痰,它的內(nèi)部同時只能夠容納單個元素。如果該隊(duì)列已有一元素的話祟蚀,試圖向隊(duì)列中插入一個新元素的線程將會阻塞工窍,直到另一個線程將該元素從隊(duì)列中抽走。同樣前酿,如果該隊(duì)列為空患雏,試圖向隊(duì)列中抽取一個元素的線程將會阻塞,直到另一個線程向隊(duì)列中插入了一條新的元素罢维。
據(jù)此淹仑,把這個類稱作一個隊(duì)列顯然是夸大其詞了。它更多像是一個匯合點(diǎn)肺孵。
8. 阻塞雙端隊(duì)列 BlockingDeque
java.util.concurrent 包里的 BlockingDeque 接口表示一個線程安放入和提取實(shí)例的雙端隊(duì)列匀借。本小節(jié)我將給你演示如何使用 BlockingDeque。BlockingDeque 類是一個雙端隊(duì)列平窘,在不能夠插入元素時吓肋,它將阻塞住試圖插入元素的線程;在不能夠抽取元素時瑰艘,它將阻塞住試圖抽取的線程是鬼。deque(雙端隊(duì)列) 是 “Double Ended Queue” 的縮寫。因此紫新,雙端隊(duì)列是一個你可以從任意一端插入或者抽取元素的隊(duì)列均蜜。
BlockingDeque 的使用
在線程既是一個隊(duì)列的生產(chǎn)者又是這個隊(duì)列的消費(fèi)者的時候可以使用到 BlockingDeque。如果生產(chǎn)者線程需要在隊(duì)列的兩端都可以插入數(shù)據(jù)芒率,消費(fèi)者線程需要在隊(duì)列的兩端都可以移除數(shù)據(jù)兆龙,這個時候也可以使用 BlockingDeque。
一個 BlockingDeque – 線程在雙端隊(duì)列的兩端都可以插入和提取元素敲董。
一個線程生產(chǎn)元素紫皇,并把它們插入到隊(duì)列的任意一端。如果雙端隊(duì)列已滿腋寨,插入線程將被阻塞聪铺,直到一個移除線程從該隊(duì)列中移出了一個元素。如果雙端隊(duì)列為空萄窜,移除線程將被阻塞铃剔,直到一個插入線程向該隊(duì)列插入了一個新元素撒桨。
BlockingDeque 的方法
BlockingDeque 具有 4 組不同的方法用于插入、移除以及對雙端隊(duì)列中的元素進(jìn)行檢查键兜。如果請求的操作不能得到立即執(zhí)行的話凤类,每個方法的表現(xiàn)也不同普气。這些方法如下:
四組不同的行為方式解釋:
拋異常:如果試圖的操作無法立即執(zhí)行谜疤,拋一個異常。
特定值:如果試圖的操作無法立即執(zhí)行现诀,返回一個特定的值(常常是 true / false)夷磕。
阻塞:如果試圖的操作無法立即執(zhí)行,該方法調(diào)用將會發(fā)生阻塞仔沿,直到能夠執(zhí)行坐桩。
超時:如果試圖的操作無法立即執(zhí)行,該方法調(diào)用將會發(fā)生阻塞封锉,直到能夠執(zhí)行绵跷,但等待時間不會超過給定值。返回一個特定值以告知該操作是否成功(典型的是 true / false)成福。
BlockingDeque 繼承自 BlockingQueue
BlockingDeque 接口繼承自 BlockingQueue 接口碾局。
這就意味著你可以像使用一個 BlockingQueue 那樣使用 BlockingDeque。如果你這么干的話闷叉,各種插入方法將會把新元素添加到雙端隊(duì)列的尾端,而移除方法將會把雙端隊(duì)列的首端的元素移除脊阴。正如 BlockingQueue 接口的插入和移除方法一樣握侧。
以下是 BlockingDeque 對 BlockingQueue 接口的方法的具體內(nèi)部實(shí)現(xiàn):
BlockingDeque 的實(shí)現(xiàn)
既然 BlockingDeque 是一個接口,那么你想要使用它的話就得使用它的眾多的實(shí)現(xiàn)類的其中一個嘿期。java.util.concurrent 包提供了以下 BlockingDeque 接口的實(shí)現(xiàn)類:
BlockingDeque 代碼示例
以下是如何使用 BlockingDeque 方法的一個簡短代碼示例:
9. 鏈阻塞雙端隊(duì)列 LinkedBlockingDeque
LinkedBlockingDeque 類實(shí)現(xiàn)了 BlockingDeque 接口品擎。
deque(雙端隊(duì)列) 是 “Double Ended Queue” 的縮寫。因此备徐,雙端隊(duì)列是一個你可以從任意一端插入或者抽取元素的隊(duì)列萄传。(譯者注:唐僧啊,受不了蜜猾。)LinkedBlockingDeque 是一個雙端隊(duì)列秀菱,在它為空的時候,一個試圖從中抽取數(shù)據(jù)的線程將會阻塞蹭睡,無論該線程是試圖從哪一端抽取數(shù)據(jù)衍菱。以下是 LinkedBlockingDeque 實(shí)例化以及使用的示例: