一蚌吸、前言
在學(xué)習(xí)DelayQueue之前圾亏,我們先來(lái)熟悉下Queue相關(guān)的一些基礎(chǔ)接口类少。
1. Queue接口
??Queue(隊(duì)列)叙身,前面學(xué)習(xí)集合框架的時(shí)候已經(jīng)了解過(guò),是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)(FIFO, First In First Out)硫狞,同樣繼承自Collection集合信轿,隊(duì)列有頭指針head和尾指針tail,數(shù)據(jù)從隊(duì)尾入隊(duì)残吩,從對(duì)頭出隊(duì)财忽。簡(jiǎn)單介紹了隊(duì)列的概念,我們來(lái)看下Queue接口中的通用方法泣侮。
??Queue接口提供了插入即彪,獲取,移除三個(gè)方法活尊,每個(gè)方法都以兩種形式存在:一種在操作失敗時(shí)拋出異常隶校,另一種在操作失敗時(shí)返回特殊值(null或false漏益,具體取決于操作),后一種形式的插入操作是專(zhuān)門(mén)用于有容量限制的隊(duì)列的深胳;
拋出異常 | 返回特殊值 | |
---|---|---|
添加 | add(e) | offer(e) |
刪除 | remove() | poll() |
獲取 | element() | peek() |
Queue接口中绰疤,共有以上6個(gè)方法:
- add, 常規(guī)的添加元素的方法,如果添加元素的時(shí)候由于容量的限制添加失敗稠屠,則會(huì)拋出異常峦睡;
- offer, 添加元素,當(dāng)隊(duì)列有容量限制并且隊(duì)列已滿(沒(méi)有可用空間)的時(shí)候权埠,該方法會(huì)返回false榨了,而對(duì)應(yīng)的add方法則是拋出異常;所以在使用有容量限制的隊(duì)列時(shí)攘蔽,建議使用offer方法龙屉;
- remove, 刪除方法,刪除并返回隊(duì)列的頭部元素满俗,如果隊(duì)列為空转捕,拋出異常;
- poll, 刪除方法唆垃, 刪除并返回隊(duì)列的頭部元素五芝,和remove方法的不同之處在于,隊(duì)列為空時(shí)辕万,remove方法拋出異常枢步,而poll方法返回null;
- element, 返回隊(duì)列的頭部元素渐尿,但不刪除醉途;
- peek, 返回隊(duì)列的頭部元素,但不刪除砖茸,和element方法的不同在于隘擎,隊(duì)列為空時(shí),element會(huì)拋出異常凉夯,而peek則會(huì)返回null货葬;
從上面的介紹可以看出,poll和remove劲够,element和peek方法的不同就在于當(dāng)隊(duì)列為空時(shí)的處理情況震桶。
隊(duì)列實(shí)現(xiàn)通常不允許插入null元素,雖然某些實(shí)現(xiàn)(如LinkedList)允許插入null再沧。即便在允許它的實(shí)現(xiàn)中尼夺,也不應(yīng)將null插入到Queue中,因?yàn)閚ull會(huì)被poll等方法用作特殊返回值,用來(lái)表示隊(duì)列不包含任何元素淤堵。
2. Deque接口
??Deque寝衫,被稱為雙端隊(duì)列,隊(duì)列的原則是只能一頭入隊(duì)拐邪,一頭出隊(duì)慰毅,而雙端隊(duì)列則是兩端都可以入隊(duì)和出隊(duì)的隊(duì)列。Deque擴(kuò)展自Queue扎阶,并且Deque也可以用作LIFO(后進(jìn)先出)汹胃,也就是棧的操作,所以官方建議我們應(yīng)該優(yōu)先使用該接口而不是Stack來(lái)進(jìn)行棧的操作东臀。由于前面已經(jīng)學(xué)習(xí)過(guò)着饥,這里不多講了,我們還是來(lái)看下它的方法惰赋。
Head | Head | Tail | Tail | |
---|---|---|---|---|
拋出異常 | 返回特殊值 | 拋出異常 | 返回特殊值 | |
添加 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
刪除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
獲取 | getFirst() | peekFirst() | getLast() | peekLast() |
由于雙端隊(duì)列可以操作對(duì)頭和隊(duì)尾宰掉,所以針對(duì)添加、刪除赁濒、獲取這三種情況轨奄,除了Queue接口中繼承的三個(gè)方法,還有分別針對(duì)隊(duì)頭和隊(duì)尾進(jìn)行操作的特殊方法拒炎。而從Queue接口繼承的方法與Deque中的一些方法是完全等效的挪拟,對(duì)應(yīng)關(guān)系如下表所示:
Queue方法 | 等效Deque方法 |
---|---|
add(e) | addLast(e) |
offer(e) | offerLast(e) |
remove() | removeFirst() |
poll() | pollFirst() |
element() | getFirst() |
peek() | peekFirst() |
上述這些方法一般用于隊(duì)列(FIFO)操作,而該接口中還提供了一些用于棧(LIFO)操作的方法击你, 當(dāng)Deque用作棧時(shí)玉组,元素將從雙端隊(duì)列的頭部進(jìn)行操作元素。 同樣果漾,Stack中對(duì)棧操作的一些常規(guī)方法與Deque中一些方法是完全等效的球切,我們來(lái)看下對(duì)應(yīng)關(guān)系:
Stack方法 | 等效Deque方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
需要注意谷誓,peek方法绒障,無(wú)論Deque是用作隊(duì)列或時(shí)棧硬萍,該方法都是有效的尚猿。另外抡诞,該接口提供了兩種方法來(lái)刪除內(nèi)部元素:
- removeFirstOccurrence帚戳,從雙端隊(duì)列中刪除第一次出現(xiàn)的指定元素相叁;
- removeLastOccurrence敞映,從雙端隊(duì)列中刪除最后一次出現(xiàn)的指定元素朱嘴;
此外返奉,Deque針對(duì)棧
的使用還提供了兩個(gè)方法:
- pop变逃,返回棧頂?shù)脑乇啬妫簿褪菑碾p端隊(duì)列中刪除并返回第一個(gè)元素(頭部),該方法等同于
removeFirst
方法;- push名眉,往棧里添加元素粟矿,也就是在雙端隊(duì)列的頭部添加元素,如果沒(méi)有可用空間導(dǎo)致添加失敗時(shí)會(huì)拋出異常损拢;該方法等同于
addFirst
方法陌粹;
還有,Deque針對(duì)Collection 的常規(guī)操作福压,還實(shí)現(xiàn)或者提供了一些方法:
- remove(Object)掏秩,從此雙端隊(duì)列中刪除第一次出現(xiàn)的指定元素,等同于
removeFirstOccurrence
方法;- iterator荆姆,以適當(dāng)?shù)捻樞蚍祷卮穗p端隊(duì)列中元素的迭代器蒙幻,遍歷順序是從頭部到尾部;
- descendingIterator胆筒,以相反的順序返回此雙端隊(duì)列中元素的迭代器杆煞,順序?yàn)閺奈膊康筋^部;
最后需要注意的是腐泻,與List接口不同决乎,此接口不支持對(duì)元素的索引訪問(wèn);雖然Deque實(shí)現(xiàn)并不嚴(yán)格要求禁止插入null元素派桩,但官方強(qiáng)烈建議我們?cè)谑褂肈eque時(shí)禁止插入null元素构诚。
3. BlockingQueue接口
??BlockingQueue表示阻塞隊(duì)列,該接口擴(kuò)展了Queue接口铆惑,并提供了幾個(gè)阻塞方法范嘱,用于實(shí)現(xiàn):當(dāng)生產(chǎn)者向隊(duì)列添加元素但隊(duì)列已滿時(shí),生產(chǎn)者會(huì)被阻塞员魏;當(dāng)消費(fèi)者從隊(duì)列移除元素但隊(duì)列為空時(shí)丑蛤,消費(fèi)者會(huì)被阻塞。而相對(duì)Queue接口撕阎,該接口的方法有四種表示形式:
拋出異常 | 返回特殊值 | 阻塞 | 超時(shí) | |
---|---|---|---|---|
添加 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
刪除 | remove() | poll() | take() | poll(time, unit) |
獲取 | element() | peek() |
當(dāng)然受裹,BlockingQueue不支持null值,null是用作標(biāo)記的值虏束。
BlockingQueue可以有容量大小限制棉饶,超過(guò)容量大小的時(shí)候,不能在沒(méi)有阻塞的情況下插入元素镇匀,BlockingQueue的實(shí)現(xiàn)主要用于生產(chǎn)者-消費(fèi)者隊(duì)列照藻,并且,BlockingQueue可以安全地與多個(gè)生產(chǎn)者和多個(gè)消費(fèi)者一起使用汗侵。
BlockingQueue是線程安全的幸缕,所有的方法都使用內(nèi)部鎖或者其他形式的并發(fā)控制以原子方式實(shí)現(xiàn)其效果群发,但是,除非在實(shí)現(xiàn)中另有說(shuō)明发乔,否則批量操作addAll也物,containsAll,retainAll和removeAll不一定以原子方式執(zhí)行列疗。
下面對(duì)該接口的其他方法簡(jiǎn)介再介紹下:
- put(E)滑蚯,添加元素,該方法是阻塞方法抵栈,必要時(shí)會(huì)阻塞直到隊(duì)列的容量空間可用告材;
- take(),刪除并返回頭部元素古劲,該方法是阻塞方法斥赋,必要時(shí)會(huì)阻塞,直到隊(duì)列中的元素可用产艾;
- remainingCapacity()疤剑,- 返回理想情況下(沒(méi)有內(nèi)存和資源約束的情況下)此隊(duì)列剩余可以無(wú)阻塞添加元素的數(shù)量,但是闷堡,我們不能總是通過(guò)檢查remainingCapacity來(lái)判斷插入元素的嘗試是否成功隘膘,因?yàn)榭赡苡辛硪粋€(gè)線程即將插入或刪除元素的情況。
- drainTo(Collection)杠览,- 刪除此隊(duì)列中所有元素弯菊,并將這些元素添加到給定集合中;另一個(gè)兩個(gè)參數(shù)的重載方法踱阿,則表示管钳,從該隊(duì)列中刪除最多給定數(shù)量的元素,并將這些元素添加到給定集合中软舌;
- poll(long, TimeUnit)才漆,獲取并刪除隊(duì)列的頭部元素,超時(shí)方法佛点,在必要時(shí)會(huì)等待指定的時(shí)間以使元素可用醇滥;
- offer(E, long, TimeUnit),添加元素恋脚,超時(shí)方法腺办,在必要時(shí)會(huì)等待指定的時(shí)間以使隊(duì)列空間可用焰手;
4. BlockingDeque接口
??BlockingDeque糟描,阻塞雙端隊(duì)列,擴(kuò)展自BlockingQueue與Deque书妻,由于是雙端隊(duì)列船响,所以它的方法針對(duì)頭部和尾部操作躬拢,各有四種形式,我們首先來(lái)看下針對(duì)頭部進(jìn)行操作的:
拋出異常 | 返回特殊值 | 阻塞 | 超時(shí) | |
---|---|---|---|---|
添加 | addFirst(e) | offerFirst(e) | putFirst(e) | offerFirst(e, time, unit) |
刪除 | removeFirst() | pollFirst() | takeFirst() | pollFirst(time, unit) |
獲取 | getFirst() | peekFirst() |
同樣见间,針對(duì)尾部的操作聊闯,就是將對(duì)應(yīng)的First修改為L(zhǎng)ast:
拋出異常 | 返回特殊值 | 阻塞 | 超時(shí) | |
---|---|---|---|---|
添加 | addLast(e) | offerLast(e) | putLast(e) | offerLast(e, time, unit) |
刪除 | removeLast() | pollLast() | takeLast() | pollLast(time, unit) |
獲取 | getLast() | peekLast() |
與BlockingQueue一樣,BlockingDeque也是線程安全的米诉,不允許存儲(chǔ)null元素菱蔬,可以有容量限制。一些從BlockingQueue接口繼承的方法與BlockingDeque方法是完全等效的史侣,我們來(lái)看下這些方法:
5. TransferQueue接口
??TransferQueue擴(kuò)展自BlockingQueue拴泌,是一個(gè)特殊的阻塞隊(duì)列。當(dāng)我們使用BlockingQueue時(shí)惊橱,我們只需要關(guān)注的是將元素放進(jìn)隊(duì)列(如果隊(duì)列已滿蚪腐,則進(jìn)行阻塞)即可,而TransferQueue則是對(duì)BlockingQueue進(jìn)行了增強(qiáng)税朴,借助該接口我們可以實(shí)現(xiàn):生產(chǎn)者會(huì)一直阻塞直到所添加到隊(duì)列的元素被某一個(gè)消費(fèi)者所消費(fèi)(不僅僅是添加到隊(duì)列里就完事)回季。
另外,該接口截止到JDK8正林,只有一個(gè)實(shí)現(xiàn)類(lèi)LinkedTransferQueue泡一,這是個(gè)無(wú)界的阻塞隊(duì)列,后面我們會(huì)介紹到這個(gè)類(lèi)觅廓。我們先來(lái)看下他提供的方法:
- transfer(E)瘾杭,阻塞方法,在必要的情況下會(huì)阻塞哪亿,直到元素被消費(fèi)者消費(fèi)粥烁;
- 當(dāng)有消費(fèi)者線程阻塞等待時(shí),調(diào)用transfer方法的生產(chǎn)者線程不會(huì)將元素存入隊(duì)列蝇棉,而是直接將元素傳遞給消費(fèi)者讨阻;
- 如果調(diào)用transfer方法的生產(chǎn)者線程發(fā)現(xiàn)沒(méi)有正在等待的消費(fèi)者線程,則會(huì)將元素入隊(duì)篡殷,然后會(huì)阻塞等待钝吮,直到有一個(gè)消費(fèi)者線程來(lái)獲取該元素;
- 在隊(duì)列中已有元素的情況下板辽,調(diào)用transfer方法奇瘦,還可以確保隊(duì)列中被傳遞元素之前的所有元素都能被處理;
- tryTransfer(E)劲弦,當(dāng)調(diào)用tryTransfer方法時(shí)耳标,如果有消費(fèi)者正在等待接收元素,直接將元素傳送給消費(fèi)者邑跪;如果沒(méi)有次坡,則會(huì)立即返回false呼猪。該方法和transfer方法的區(qū)別在于tryTransfer方法無(wú)論消費(fèi)者是否接收,方法立即返回砸琅,元素不會(huì)入隊(duì)宋距;而transfer方法必須等到消費(fèi)者消費(fèi)后才返回;
- tryTransfer(E, long, TimeUnit)症脂,超時(shí)方法谚赎,如果有消費(fèi)者正在等待接收元素,則理解將元素給消費(fèi)者诱篷;如果在這一段超時(shí)時(shí)間內(nèi)還沒(méi)有消費(fèi)者消費(fèi)元素沸版,則返回false;如果在超時(shí)時(shí)間內(nèi)消費(fèi)了元素兴蒸,則返回true视粮;
- hasWaitingConsumer(),判斷是否有消費(fèi)者正在等待接收元素橙凳,如果至少有一個(gè)消費(fèi)者正在等待通過(guò)BlockingQueue.take()或定時(shí)輪詢接收元素蕾殴,則返回true;
- getWaitingConsumerCount()岛啸,返回通過(guò)BlockingQueue.take()或定時(shí)輪詢等待接收元素的消費(fèi)者數(shù)量的估計(jì)值钓觉,該值是一個(gè)瞬時(shí)值,可能不準(zhǔn)確坚踩,該值可用于監(jiān)控使用荡灾,但不適合用于程序中同步控制;
最后瞬铸,我們簡(jiǎn)單看一個(gè)小例子:
public static void testTransferQueue() throws InterruptedException {
TransferQueue<String> transferQueue = new LinkedTransferQueue<>();
transferQueue.add("hello");
new Thread(() -> {
try {
transferQueue.transfer("world");
System.out.println("new Thread ->> ");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
String str = transferQueue.take();
// output hello
System.out.println("finish ->> " + str);
// 以下代碼可以先注釋掉批幌,看一下運(yùn)行情況,再去掉注釋運(yùn)行
// str = transferQueue.take();
// System.out.println("finish ->> " + str);
}
本文主要參考自:
JDK8官方API:https://docs.oracle.com/javase/8/docs/api/
Java 7中的TransferQueue - 并發(fā)編程網(wǎng)
Java多線程進(jìn)階(三八)—— J.U.C之collections框架:LinkedTransferQueue