翻譯自http://tutorials.jenkov.com/java-util-concurrent/index.html
相關文章:
Java 5 添加了一個新java包到Java平臺,java.util.concurrent
。這個包含有一系列的類使通過Java來開發并發(多線程)應用程序更為簡單方便。在這個包被添加前你不得不自己創建相關工具類。
一、BlockingQueue 阻塞隊列
java.util.concurrent
里面的BlockingQueue
接口代表一個線程安全的隊列。
BlockingQueue
通常用于一個線程生成對象,另一個線程消費。
生產線程持續生成新的對象并且把它插入都隊列直到隊列達到它容納的上限。如果這個阻塞隊列達到容量上限,生產線程再嘗試插入新的對象時會阻塞。它會一直阻塞直到一個消費線程從隊列中取走一個對象。
消費線程會持續從阻塞隊列中取出對象然后處理它們。如果一個消費線程嘗試從一個空的隊列中取對象,它就會被阻塞直到一個生產線程向隊列中放入一個對象為止。
1.1 BlockingQueue方法
BlockingQueue
有4套不同的方法集合來插入、刪除;2套方法來檢查隊列中的元素。每套不同方法的行為取決于請求的操作是否立即執行。這4套方法的區別是:
- Throws Exception 如果企圖的操作不可能立即完成,那么會拋出一個異常。
- Special Value 如果企圖的操作不可能立即完成,那么會返回一個特殊的值(通常是
true
/false
)。 - Blocks 如果企圖的操作不可能立即完成,這個方法會阻塞,直到可以繼續進行。
- Times Out 如果企圖的操作不可能立即完成,這個方法會阻塞,但是阻塞的時間最長不會超過指定的timeout值,達到timeout后會返回一個特殊的值(通常是
true
/false
)來告訴你操作是否成功。

向BlockingQueue
插入null
是不可能的。如果你嘗試去插入一個null
,BlockingQueue
會拋出NullPointerException
。
也可以去獲得BlockingQueue
里所有的元素,而不僅僅是在隊首或者隊尾的元素。舉個例子,你將一個對象入隊后,但是你的程序決定要取消這個操作,這樣你就可以使用比如:remove(o)
這個方法來移除這個特殊的對象。但是這樣的操作效率不高,所以你不應該用這些Collection
方法除非你確實需要。
1.2 BlockingQueue實現
既然BlockingQueue
是一個接口,你在實際使用中需要使用它的某個是實現。BlockingQueue
接口有以下幾個實現類(在Java 6):
ArrayBlockingQueue
DelayQueue
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
1.2.1 ArrayBlockingQueue
ArrayBlockingQueue
是一個有邊界的連續的隊列,在內部是通過一個數組存儲元素的。有邊界意味著它不能無限制的存儲元素。同時在實例化時需要指定存儲的元素個數,一旦實例化完成這個上限不可修改。
ArrayBlockingQueue
存儲元素遵循FIFO(先進先出)順序。示例:
BlockingQueue queue = new ArrayBlockingQueue(1024);
queue.put("1");
Object object = queue.take();
// 或者使用泛型
BlockingQueue<String> queue = new ArrayBlockingQueue<String>(1024);
queue.put("1");
String string = queue.take();
1.2.2 DelayQueue
DelayQueue
內部會阻塞元素直到一個確定的延遲過期后。隊列里的元素必須實現java.util.concurrent.Delayed
接口,下面就是這個接口:
public interface Delayed extends Comparable<Delayed< {
public long getDelay(TimeUnit timeUnit);
}
getDelay()
方法返回的值是在這個元素可以被釋放前延遲剩余的時間。如果0或者一個負數被返回了,延遲時間會被認為已經過期,這個元素將會在下一個調用隊列的take()
等取出方法時被釋放。
1.2.3 LinkedBlockingQueue
LinkedBlockingQueue
實現了BlockingQueue
接口。它內部保存元素采用了一種鏈式結構(鏈式節點)。這種鏈式結構如果需要可選擇性設置上限。如果沒有指定上限,Integer.MAX_VALUE
將會被作為上限。
LinkedBlockingQueue
存儲元素時遵循FIFO(First In, First Out)的順序。
下面是相關示例:
BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>();
BlockingQueue<String> bounded = new LinkedBlockingQueue<String>(1024);
bounded.put("Value");
String value = bounded.take();
1.2.4 PriorityBlockingQueue
PriorityBlockingQueue
是一個無界的并發隊列。它使用與java.util.PriorityQueue
相同的排序規則。你不能插入null
到這個隊列。插入到PriorityBlockingQueue
的所有元素必須實現java.lang.Comparable
接口。元素根據你在Comparable
實現里決定的優先級排列它們自己。
注意PriorityBlockingQueue
對于有相同的優先級(compare() == 0)的元素沒有強迫任何特殊的行為。
還應該注意,假如你從PriorityBlockingQueue
獲得到一個Iterator
,這個Iterator
沒有保證在優先級順序下遍歷元素。
這里是一個示例:
BlockingQueue queue = new PriorityBlockingQueue();
//String實現了java.lang.Comparable
queue.put("Value");
String value = queue.take();
1.2.5 SynchronousQueue
SynchronousQueue
隊列內部只能容納一個元素。一個線程如果插入一個元素到這個隊列就會阻塞,知道另一個線程從隊列中取出。類似的,如果一個線程嘗試取出一個元素但是隊列里面現在沒有元素,這個線程就會被阻塞直到一個線程插入一個元素到隊列。
1.3 BlockingQueue示例
生產者類。注意每個put()
之間的線程休眠的時間,在消費者等待從隊列中取對象時將會引起阻塞。
public class Producer implements Runnable{
protected BlockingQueue queue = null;
public Producer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
try {
queue.put("1");
Thread.sleep(1000);
queue.put("2");
Thread.sleep(1000);
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
下面是消費者類。它只是從隊列中取出對象并使用System.out
打印它們。
public class Consumer implements Runnable{
protected BlockingQueue queue = null;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
try {
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
BlockingQueueExample
類會分別開啟一個生產者類和一個消費者類的線程。生產者向一個共享的BlockingQueue
插入字符串,同時消費者類從中取出。
public class BlockingQueueExample {
public static void main(String[] args) throws Exception {
BlockingQueue queue = new ArrayBlockingQueue(1024);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
new Thread(producer).start();
new Thread(consumer).start();
Thread.sleep(4000);
}
}
二、BlockingDeque 阻塞雙端隊列
BlockingDeque
接口代表一個雙端隊列,它對于插入與取出的操作都是線程安全的。
2.1 BlockingDeque使用
BlockingDeque
可以這樣被使用,如果所有線程都同時既生產也消費同一個隊列的元素。也同樣適用于生產線程需要在隊列兩端插入,消費線程需要從隊列兩端取出。
一個線程生產元素并且可以將它們插入隊列的兩端。如果雙端隊列當前是滿的,這個插入線程將會被阻塞直到一個消費線程從隊列取出一個元素,當消費線程從中取的時候也是類似的情況。
2.2 BlockingDeque方法
BlockingDeque
有4種不同系列的方法用來插入、刪除和檢查在雙端隊列中的元素。
這四套不同的行為如下,與BlockingQueue
類似:
- Throws Exception 如果企圖的操作不可能立即完成,那么會拋出一個異常。
- Special Value 如果企圖的操作不可能立即完成,那么會返回一個特殊的值(通常是
true
/false
)。 - Blocks 如果企圖的操作不可能立即完成,這個方法會阻塞,直到可以繼續進行。
- Times Out 如果企圖的操作不可能立即完成,這個方法會阻塞,但是阻塞的時間最長不會超過指定的timeout值,達到timeout后會返回一個特殊的值(通常是
true
/false
)來告訴你操作是否成功。
2.3 BlockingDeque繼承自BlockingQueue
BlockingDeque
接口繼承自BlockingQueue
接口。這意味這你可以將BlockingDeque
當作BlockingQueue
用。
下面是一個表格來展示BlockingQueue
的方法在BlockingDeque
的實現里是如何工作的:
2.4 BlockingDeque實現類
既然BlockingDeque
是一個接口,你就需要使用它的某個實現類。java.util.concurrent
包有以下實現類:
LinkedBlockingDeque
2.5 BlockingDeque代碼示例
下面是一個簡單的例子來展示如何使用它的方法:
BlockingDeque<String> deque = new LinkedBlockingDeque<String>();
deque.addFirst("1");
deque.addLast("2");
String two = deque.takeLast();
String one = deque.takeFirst();