簡介
Queue是什麼就不需要多說了吧,一句話:隊列是先進先出。相對的,棧是後進先出。如果不熟悉的話先找本基礎的數據結構的書看看吧。
BlockingQueue,顧名思義,“阻塞隊列”:可以提供阻塞功能的隊列。
首先,看看BlockingQueue提供的常用方法:
可能報異常 | 返回布爾值 | 可能阻塞 | 設定等待時間 | |
入隊 | add(e) | offer(e) | put(e) | offer(e, timeout, unit) |
出隊 | remove() | poll() | take() | poll(timeout, unit) |
查看 | element() | peek() | 無 | 無 |
從上表可以很明顯看出每個方法的作用,這個不用多說。我想說的是:
- add(e) remove() element() 方法不會阻塞線程。當不滿足約束條件時,會拋出IllegalStateException 異常。例如:當隊列被元素填滿後,再調用add(e),則會拋出異常。
- offer(e) poll() peek() 方法即不會阻塞線程,也不會拋出異常。例如:當隊列被元素填滿後,再調用offer(e),則不會插入元素,函數返回false。
- 要想要實現阻塞功能,需要調用put(e) take() 方法。當不滿足約束條件時,會阻塞線程。
ArrayBlockingQueue:基於數組的阻塞隊列實現,在ArrayBlockingQueue內部,維護了一個定長數組,以便緩存隊列中的數據對象,其內部沒實現讀寫分離,也就意味着生產和消費不能完全並行,長度是需要定義的,可以指定先講先出或者先講後出,也叫有界隊列,在很多場合非常適合使用。
LinkedBlockingQueue:基於鏈表的阻塞隊列,同ArrayBlockingQueue類似,其內部也維持着一個數據緩衝隊列〈該隊列由一個鏈表構成),LinkedBlockingQueue之所以能夠高效的處理併發數據,是因爲其內部實現採用分離鎖(讀寫分離兩個鎖),從而實現生產者和消費者操作的完全並行運行,他是一個無界隊列。
SynchronousQueue:一種沒有緩衝的隊列,生產者產生的數據直接會被消費者獲取並消費。
PriorityBlockingQueue:基於優先級的阻塞隊列(優先級的判斷通過構造函數傳入的Compator對象來決定,也就是說傳入隊列的對象必須實現Comparable接口),在實現PriorityBlockingQueue時,內部控制線程同步的鎖採用的是公平鎖,他也是一個無界的隊列。
DelayQueue:帶有延遲時間的Queue,其中的元素只有當其指定的延遲時間到了,才能夠從隊列中獲取到該元素。DelayQueue中的元素必須實現Delayed接口,DelayQueue是一個沒有大小限制的隊列,應用場景很多,比如對緩存超時的數據進行移除、任務超時處理、空閒連接的關閉等等。
在併發編程中,一般推薦使用阻塞隊列,這樣實現可以儘量地避免程序出現意外的錯誤。阻塞隊列使用最經典的場景就是socket客戶端數據的讀取和解析,讀取數據的線程不斷將數據放入隊列,然後解析線程不斷從隊列取數據解析。還有其他類似的場景,只要符合生產者-消費者模型的都可以使用阻塞隊列。
使用非阻塞隊列,雖然能即時返回結果(消費結果),但必須自行編碼解決返回爲空的情況處理(以及消費重試等問題)。
另外他們都是線程安全的,不用考慮線程同步問題。
生產者-消費者例子
package com.spark.demo.sub.queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingDeque {
// 阻塞隊列,FIFO
private static LinkedBlockingQueue<Integer> concurrentLinkedQueue = new LinkedBlockingQueue<Integer>();
public static void main(final String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.submit(new Consumer("consumer1",concurrentLinkedQueue));
executorService.submit(new Producer("producer1",concurrentLinkedQueue));
executorService.submit(new Producer("producer2",concurrentLinkedQueue));
//executorService.submit(new Consumer("consumer3",concurrentLinkedQueue));
}
}
package com.spark.demo.sub.queue;
import java.util.concurrent.LinkedBlockingQueue;
public class Producer implements Runnable {
private String name;
private LinkedBlockingQueue<Integer> concurrentLinkedQueue;
public Producer(final String name, final LinkedBlockingQueue<Integer> concurrentLinkedQueue) {
this.name = name;
this.concurrentLinkedQueue= concurrentLinkedQueue;
}
public void run() {
for (int i = 1; i < 10; ++i) {
System.out.println(name + " 生產: " + i);
// concurrentLinkedQueue.add(i);
try {
concurrentLinkedQueue.put(i);
Thread.sleep(2000); // 模擬慢速的生產,產生阻塞的效果
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
package com.spark.demo.sub.queue;
import java.util.concurrent.LinkedBlockingQueue;
class Consumer implements Runnable {
private String name;
private LinkedBlockingQueue<Integer> concurrentLinkedQueue;
public Consumer(final String name, final LinkedBlockingQueue<Integer> concurrentLinkedQueue) {
this.name = name;
this.concurrentLinkedQueue= concurrentLinkedQueue;
}
public void run() {
while (true) {
try {
// 必須要使用take()方法在獲取的時候阻塞
System.out.println(name + "消費: " + concurrentLinkedQueue.take());
// 使用poll()方法 將產生非阻塞效果
// System.out.println(name+"消費: " +
// concurrentLinkedQueue.poll());
// 還有一個超時的用法,隊列空時,指定阻塞時間後返回,不會一直阻塞
// 但有一個疑問,既然可以不阻塞,爲啥還叫阻塞隊列?
// System.out.println(name+" Consumer " +
// concurrentLinkedQueue.poll(300, TimeUnit.MILLISECONDS));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}