BlockingQueue
先進先出,出隊列即移除。可用來實現消費者模式
BlockingQueue的核心方法:
放入數據:
offer(anObject):表示如果可能的話,將anObject加到BlockingQueue裏,即如果BlockingQueue可以容納,則返回true,否則返回false.(本方法不阻塞當前執行方法的線程)
offer(E o, long timeout, TimeUnit unit),可以設定等待的時間,如果在指定的時間內,還不能往隊列中加入BlockingQueue,則返回失敗。
put(anObject):把anObject加到BlockingQueue裏,如果BlockQueue沒有空間,則調用此方法的線程被阻斷直到BlockingQueue裏面有空間再繼續.
獲取數據:
peek():返回首位對象但不刪除,如果隊列空 返回null.
poll(time):取走BlockingQueue裏排在首位的對象,若不能立即取出,則可以等time參數規定的時間,取不到時返回null;
poll(long timeout, TimeUnit unit):從BlockingQueue取出一個隊首的對象,如果在指定時間內,
隊列一旦有數據可取,則立即返回隊列中的數據。否則知道時間超時還沒有數據可取,返回失敗。
take():取走BlockingQueue裏排在首位的對象,若BlockingQueue爲空,阻斷進入等待狀態直到BlockingQueue有新的數據被加入;
drainTo():一次性從BlockingQueue獲取所有可用的數據對象(還可以指定獲取數據的個數),通過該方法,可以提升獲取數據效率;不需要多次分批加鎖或釋放鎖。
ArrayBlockingQueue
使用數組實現的FIFO阻塞隊列
構造方法
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
//依賴重入鎖實現阻塞,默認是非公平鎖 可以設置。
lock = new ReentrantLock(fair);
//使用lock創建 condition.說明 存取數據阻塞使用的是同一個鎖
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
put方法
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//數據滿的時候等待取數據執行
while (count == items.length)
notFull.await();
insert(e);//插入數據
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);// (++i == items.length) ? 0 : i; 循環寫入
++count;
notEmpty.signal();// 寫入成功後 喚醒 notEmpty.await()
}
take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//數據爲空等待放數據
while (count == 0)
notEmpty.await();
return extract();//出隊數據
} finally {
lock.unlock();
}
}
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);//取出數據 ,類型轉換
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();//喚醒 notFull.await()
return x;
}
小結:ArrayBlockingQueue 使用一把鎖來實現阻塞和等待喚醒,說明 其take(),put()的執行時串行的。也說明ArrayBlockingQueue在多線程併發下 吞吐量不高。
LinkedBlockingQueue
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {//當隊列滿的時候阻塞直到節點被出隊
notFull.await();
}
enqueue(node);//入隊節點
c = count.getAndIncrement();
if (c + 1 < capacity)喚醒其他put阻塞的線程。這個處理 take會執行,這裏執行是幫助喚醒。
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)//只有滿足了 take await的條件,才能執行喚醒。
signalNotEmpty();
}
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {//當隊列爲空 take 阻塞 直到有結點入隊。
notEmpty.await();
}
x = dequeue();//出隊頭結點
c = count.getAndDecrement();//先獲取 後減少count值
if (c > 1)//出隊前 至少有2個以上節點
notEmpty.signal();// 喚醒其他take阻塞的線程。這個處理 put會執行,這裏執行是幫助喚醒。
} finally {
takeLock.unlock();
}
if (c == capacity)//只有滿足了put await的條件,才能執行put喚醒
signalNotFull();
return x;
}
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;//獲取頭節點的下個節點
h.next = h; // help GC 頭節點的next指向自己, 出隊的常用寫法
head = first;//頭結點指向原頭節點的下個節點
E x = first.item;//獲取節點數據實例
注意 這裏獲取的是頭節點的下個節點數據。這個隊列初始化的時候是創建了空的head節點,出隊head後,原來存儲數據的節點變成了頭節點
first.item = null;//清理節點數據
return x;//返回節點數據。
}
總結:
LinkedBlockingQueue是一個阻塞隊列,內部由兩個ReentrantLock來實現出入隊列的線程安全,由各自的Condition對象的await和signal來實現等待和喚醒功能。它和ArrayBlockingQueue的不同點在於:
- 隊列大小有所不同,ArrayBlockingQueue是有界的初始化必須指定大小,而LinkedBlockingQueue可以是有界的也可以是無界的(Integer.MAX_VALUE),對於後者而言,當添加速度大於移除速度時,在無界的情況下,可能會造成內存溢出等問題。
- 數據存儲容器不同,ArrayBlockingQueue採用的是數組作爲數據存儲容器,而LinkedBlockingQueue採用的則是以Node節點作爲連接對象的鏈表。
- 由於ArrayBlockingQueue採用的是數組的存儲容器,因此在插入或刪除元素時不會產生或銷燬任何額外的對象實例,而LinkedBlockingQueue則會生成一個額外的Node對象。這可能在長時間內需要高效併發地處理大批量數據的時,對於GC可能存在較大影響。
- 兩者的實現隊列添加或移除的鎖不一樣,ArrayBlockingQueue實現的隊列中的鎖是沒有分離的,即添加操作和移除操作採用的同一個ReenterLock鎖,而LinkedBlockingQueue實現的隊列中的鎖是分離的,其添加採用的是putLock,移除採用的則是takeLock,這樣能大大提高隊列的吞吐量,也意味着在高併發的情況下生產者和消費者可以並行地操作隊列中的數據,以此來提高整個隊列的併發性能。
參考資料:
BlockingQueue核心方法:http://wsmajunfeng.iteye.com/blog/1629354
LinkedBlockingQueue和ArrayBlockingQueue 分析:https://www.jianshu.com/p/b888a1689822
LinkedBlockingQueue分析的很詳細,還有和ArrayBlockingQueue的對比:https://blog.csdn.net/tonywu1992/article/details/83419448