併發容器之BlockingQueue

1. BlockingQueue簡介

在實際編程中,會常用到JDK中Collection集合框架中的各類容器類如實現List,Map,Queue接口的容器類,可是這些容器類基本上不是線程安全的,除了使用Collections能夠將其轉換爲線程安全的容器,Doug Lea大師爲咱們都準備了對應的線程安全的容器,如實現List接口的CopyOnWriteArrayList(關於CopyOnWriteArrayList能夠看這篇文章),實現Map接口的ConcurrentHashMap(關於ConcurrentHashMap能夠看這篇文章),實現Queue接口的ConcurrentLinkedQueue(關於ConcurrentLinkedQueue能夠看這篇文章)。java

最經常使用的"生產者-消費者"問題中,隊列一般被視做線程間操做的數據容器,這樣,能夠對各個模塊的業務功能進行解耦,生產者將「生產」出來的數據放置在數據容器中,而消費者僅僅只須要在「數據容器」中進行獲取數據便可,這樣生產者線程和消費者線程就可以進行解耦,只專一於本身的業務功能便可。阻塞隊列(BlockingQueue)被普遍使用在「生產者-消費者」問題中,其緣由是BlockingQueue提供了可阻塞的插入和移除的方法。當隊列容器已滿,生產者線程會被阻塞,直到隊列未滿;當隊列容器爲空時,消費者線程會被阻塞,直至隊列非空時爲止。編程

2. 基本操做

BlockingQueue基本操做總結以下(此圖來源於JAVA API文檔):數組

BlockingQueue基本操做.png

BlockingQueue繼承於Queue接口,所以,對數據元素的基本操做有:安全

插入元素數據結構

  1. add(E e) :往隊列插入數據,當隊列滿時,插入元素時會拋出IllegalStateException異常;
  2. offer(E e):當往隊列插入數據時,插入成功返回true,不然則返回false。當隊列滿時不會拋出異常;

刪除元素框架

  1. remove(Object o):從隊列中刪除數據,成功則返回true,不然爲false
  2. poll:刪除數據,當隊列爲空時,返回null;

查看元素post

  1. element:獲取隊頭元素,若是隊列爲空時則拋出NoSuchElementException異常;
  2. peek:獲取隊頭元素,若是隊列爲空則拋出NoSuchElementException異常

BlockingQueue具備的特殊操做:spa

插入數據:線程

  1. put:當阻塞隊列容量已經滿時,往阻塞隊列插入數據的線程會被阻塞,直至阻塞隊列已經有空餘的容量可供使用;
  2. offer(E e, long timeout, TimeUnit unit):若阻塞隊列已經滿時,一樣會阻塞插入數據的線程,直至阻塞隊列已經有空餘的地方,與put方法不一樣的是,該方法會有一個超時時間,若超過當前給定的超時時間,插入數據的線程會退出;

刪除數據code

  1. take():當阻塞隊列爲空時,獲取隊頭數據的線程會被阻塞;
  2. poll(long timeout, TimeUnit unit):當阻塞隊列爲空時,獲取數據的線程會被阻塞,另外,若是被阻塞的線程超過了給定的時長,該線程會退出

3. 經常使用的BlockingQueue

實現BlockingQueue接口的有ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue,而這幾種常見的阻塞隊列也是在實際編程中會經常使用的,下面對這幾種常見的阻塞隊列進行說明:

1.ArrayBlockingQueue

ArrayBlockingQueue是由數組實現的有界阻塞隊列。該隊列命令元素FIFO(先進先出)。所以,對頭元素時隊列中存在時間最長的數據元素,而對尾數據則是當前隊列最新的數據元素。ArrayBlockingQueue可做爲「有界數據緩衝區」,生產者插入數據到隊列容器中,並由消費者提取。ArrayBlockingQueue一旦建立,容量不能改變。

當隊列容量滿時,嘗試將元素放入隊列將致使操做阻塞;嘗試從一個空隊列中取一個元素也會一樣阻塞。

ArrayBlockingQueue默認狀況下不能保證線程訪問隊列的公平性,所謂公平性是指嚴格按照線程等待的絕對時間順序,即最早等待的線程可以最早訪問到ArrayBlockingQueue。而非公平性則是指訪問ArrayBlockingQueue的順序不是遵照嚴格的時間順序,有可能存在,一旦ArrayBlockingQueue能夠被訪問時,長時間阻塞的線程依然沒法訪問到ArrayBlockingQueue。若是保證公平性,一般會下降吞吐量。若是須要得到公平性的ArrayBlockingQueue,可採用以下代碼:

private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);
複製代碼

關於ArrayBlockingQueue的實現原理,能夠看這篇文章

2.LinkedBlockingQueue

LinkedBlockingQueue是用鏈表實現的有界阻塞隊列,一樣知足FIFO的特性,與ArrayBlockingQueue相比起來具備更高的吞吐量,爲了防止LinkedBlockingQueue容量迅速增,損耗大量內存。一般在建立LinkedBlockingQueue對象時,會指定其大小,若是未指定,容量等於Integer.MAX_VALUE

3.PriorityBlockingQueue

PriorityBlockingQueue是一個支持優先級的無界阻塞隊列。默認狀況下元素採用天然順序進行排序,也能夠經過自定義類實現compareTo()方法來指定元素排序規則,或者初始化時經過構造器參數Comparator來指定排序規則。

4.SynchronousQueue

SynchronousQueue每一個插入操做必須等待另外一個線程進行相應的刪除操做,所以,SynchronousQueue實際上沒有存儲任何數據元素,由於只有線程在刪除數據時,其餘線程才能插入數據,一樣的,若是當前有線程在插入數據時,線程才能刪除數據。SynchronousQueue也能夠經過構造器參數來爲其指定公平性。

5.LinkedTransferQueue

LinkedTransferQueue是一個由鏈表數據結構構成的無界阻塞隊列,因爲該隊列實現了TransferQueue接口,與其餘阻塞隊列相比主要有如下不一樣的方法:

transfer(E e) 若是當前有線程(消費者)正在調用take()方法或者可延時的poll()方法進行消費數據時,生產者線程能夠調用transfer方法將數據傳遞給消費者線程。若是當前沒有消費者線程的話,生產者線程就會將數據插入到隊尾,直到有消費者可以進行消費才能退出;

tryTransfer(E e) tryTransfer方法若是當前有消費者線程(調用take方法或者具備超時特性的poll方法)正在消費數據的話,該方法能夠將數據當即傳送給消費者線程,若是當前沒有消費者線程消費數據的話,就當即返回false。所以,與transfer方法相比,transfer方法是必須等到有消費者線程消費數據時,生產者線程纔可以返回。而tryTransfer方法可以當即返回結果退出。

tryTransfer(E e,long timeout,imeUnit unit)
與transfer基本功能同樣,只是增長了超時特性,若是數據才規定的超時時間內沒有消費者進行消費的話,就返回false

6.LinkedBlockingDeque

LinkedBlockingDeque是基於鏈表數據結構的有界阻塞雙端隊列,若是在建立對象時爲指定大小時,其默認大小爲Integer.MAX_VALUE。與LinkedBlockingQueue相比,主要的不一樣點在於,LinkedBlockingDeque具備雙端隊列的特性。LinkedBlockingDeque基本操做以下圖所示(來源於java文檔)

LinkedBlockingDeque的基本操做.png

如上圖所示,LinkedBlockingDeque的基本操做能夠分爲四種類型:1.特殊狀況,拋出異常;2.特殊狀況,返回特殊值如null或者false;3.當線程不知足操做條件時,線程會被阻塞直至條件知足;4. 操做具備超時特性。

另外,LinkedBlockingDeque實現了BlockingDueue接口而LinkedBlockingQueue實現的是BlockingQueue,這兩個接口的主要區別以下圖所示(來源於java文檔):

BlockingQueue和BlockingDeque的區別.png

從上圖能夠看出,兩個接口的功能是能夠等價使用的,好比BlockingQueue的add方法和BlockingDeque的addLast方法的功能是同樣的。

7.DelayQueue

DelayQueue是一個存放實現Delayed接口的數據的無界阻塞隊列,只有當數據對象的延時時間達到時才能插入到隊列進行存儲。若是當前全部的數據都尚未達到建立時所指定的延時期,則隊列沒有隊頭,而且線程經過poll等方法獲取數據元素則返回null。所謂數據延時期滿時,則是經過Delayed接口的getDelay(TimeUnit.NANOSECONDS)來進行斷定,若是該方法返回的是小於等於0則說明該數據元素的延時期已滿。

相關文章
相關標籤/搜索