BlockingQueue

簡介

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();
			}

		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章