在編程中我們經常會使用到Queue容器類,但是這些容器類不是線程安全的,因此concurrent包中Doug Lea大師
爲我們準備了對應的線程安全的容器類;每一種容器類也滿足了對應的使用場景;那些本文就是梳理這些併發的Queue容
器類及使用場景;
1、主要容器實現類:
ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue
2、常用的BlockingQueue基本操作
插入操作:BlockingQueue實現的3和4
- add(E e) :往隊列插入數據,當隊列滿時,插入元素時會拋出IllegalStateException異常;
- offer(E e):當往隊列插入數據時,插入成功返回
true
,否則則返回false
。當隊列滿時不會拋出異常;- put:當阻塞隊列容量已經滿時,往阻塞隊列插入數據的線程會被阻塞,直至阻塞隊列已經有空餘的容量可供使用;
- offer(E e, long timeout, TimeUnit unit):若阻塞隊列已經滿時,同樣會阻塞插入數據的線程,直至阻塞隊列已經有空餘的地方,與put方法不同的是,該方法會有一個超時時間,若超過當前給定的超時時間,插入數據的線程會退出;
刪除操作:BlockingQueue實現的3和4
- remove(Object o):從隊列中刪除數據,成功則返回
true
,否則爲false
- poll:刪除數據,當隊列爲空時,返回null;
- take():當阻塞隊列爲空時,獲取隊頭數據的線程會被阻塞;
- poll(long timeout, TimeUnit unit):當阻塞隊列爲空時,獲取數據的線程會被阻塞,另外,如果被阻塞的線程超過了給定的時長,該線程會退出
查看操作:
- element:獲取隊頭元素,如果隊列爲空時則拋出NoSuchElementException異常;
- peek:獲取隊頭元素,如果隊列爲空則拋出NoSuchElementException異常
3、常用BlockingQueue詳解
①ArrayBlockingQueue 是由數組實現的有界阻塞隊列,隊列命令元素FIFO(先進先出);當隊列容量滿時放入元素操作會阻塞,當隊列爲null時獲取一個元素也會阻塞;
默認情況下這種隊列不能保證訪問隊列的公平性,這是爲了提升吞吐量;確保公平性可以通過在構造函數中增加true設定;
②LinkedBlockingQueue 是用鏈表實現的有界阻塞隊列,也是FIFO特性;具有更高的吞吐量; 爲了防止迅速增加耗損大量的內容,通常創建對象時會指定其初始大小,未指定則爲Interger.MAX_VALUE
③PriorityBlockingQueue
是一個支持優先級的無界阻塞隊列,元素採用自然順序進行排序,可以通過自定義類實現compareTo()方法來指定元素排序規則,或者初始化時通過構造器參數Comparator指定排序規則
④SynchrousQueue 不存儲任何元素的阻塞隊列,只有當其他線程刪除數據才能插入數據,可以通過構造器參數來指定公平性;
⑤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)</br>
與transfer基本功能一樣,只是增加了超時特性,如果數據才規定的超時時間內沒有消費者進行消費的話,就返回false
。
⑥LinkedBlockingDeque 基於鏈表數據結構的有界阻塞雙端隊列,在創建對象時爲指定大小時,其默認大小爲Integer.MAX_VALUE,
⑦DelayQueue 是一個存放實現Delayed接口的數據的無界阻塞隊列,只有當數據對象的延時時間達到時才能插入到隊列進行存儲; 當前多有的數據都還沒有達到創建時所指定的延時期,則隊列沒有隊頭,
並且線程通過poll等方法獲取數據元素則返回null。所謂數據延時期滿時,則是通過Delayed接口的getDelay(TimeUnit.NANOSECONDS)
來進行判定,如果該方法返回的是小於等於0則說明該數據元素的延時期已滿。
4,ArrayBlockingQueue源碼解析
使用案例:通過ArrayBlockingQueue實現消費者-生產者模式
package com.hezm.thread.day1.collections;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingDemo {
/***
* 利用 ArrayBlockingQueue 的添加已滿會阻塞和獲取已空會阻塞實現生產者-消費者模式
*
*/
ArrayBlockingQueue<String> ab = new ArrayBlockingQueue(10);
public void init() {
new Thread(()->{
try {
String data = ab.take(); //阻塞方式獲取數據;
System.out.println("receive:" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
public void addData(String data) {
ab.add(data);
try {
System.out.println("send:" + data);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BlockingDemo blockingDemo = new BlockingDemo();
for (int i = 0; i < 1000; i++) {
blockingDemo.addData("data:" + i);
blockingDemo.init();
}
}
}
運行結果:
send:data:0
send:data:1
receive:data:0
send:data:2
receive:data:1
send:data:3
receive:data:2
send:data:4
receive:data:3
send:data:5
receive:data:4
send:data:6
receive:data:5
send:data:7
receive:data:6
源碼分析:
/**
* 關鍵元素: notEmpty 不爲null喚醒 、 notFull 不爲滿喚醒
* 天然滿足生產者-消費者:在爲null的時候消費者阻塞等待,有值之後消費者被喚醒;
* 在隊列滿的時候生產者阻塞等待,不滿的時候喚醒生產;
*
*add{
* offer(){
* 重入鎖{ enqueue(e) 插入隊列
* if (++putIndex == items.length) putIndex = 0; //當要插入的數組下標滿了則重置爲0;
* notEmpty.signal(); //去喚醒 take中的 notEmpty.await()
* }
* }
* }
*
*
* take{
* 重入鎖中獲取可中斷的鎖;
* notEmpty.await() 阻塞等待隊列中設置進值
* dequeue() 出隊列;{
* if (++takeIndex == items.length) takeIndex = 0; 獲取隊列的數組下標;
* itrs.elementDequeued(); // 維護迭代器中的內容;
* notFull.signal();
* }
* }
* remove() 移除
*/
//add() 往隊列添加元素
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock; //重入鎖鎖定資源
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e); //插入隊列
return true;
}
} finally {
lock.unlock();
}
}
private void enqueue(E x) { //入隊列
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) //putIndex 當插入下標已滿則重置爲0,循環使用;
putIndex = 0;
count++;
notEmpty.signal(); //非null喚醒;
}
//take() 阻塞等待隊列數據進行消費
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //重入鎖-可中斷的鎖獲取
try {
while (count == 0)
notEmpty.await(); //當隊列數爲0時非null等待
return dequeue(); //出隊列
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) //出隊已滿則重置讀取下標爲0;
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); //未滿隊列等待
return x;
}