阻塞隊列 BlockingQueue
BlockingQueue用法
-
BlockingQueue 通常用于一個線程生產對象,而另外一個線程消費這些對象的場景。下圖是
對這個原理的闡述: 一個線程將會持續生產新對象并將其插入到隊列之中,直到隊列達到它所能容納的臨界點。
也就是說,它是有限的。如果該阻塞隊列到達了其臨界點,負責生產的線程將會在往里邊插
入新對象時發生阻塞。它會一直處于阻塞之中,直到負責消費的線程從隊列中拿走一個對象。
負責消費的線程將會一直從該阻塞隊列中拿出對象。如果消費線程嘗試去從一個空的隊列中
提取對象的話,這個消費線程將會處于阻塞之中,直到一個生產線程把一個對象丟進隊列。
BlockingQueue 的方法
- BlockingQueue 具有 4 組不同的方法用于插入、移除以及對隊列中的元素進行檢查。如果請求的操作不能得到立即執行的話,每個方法的表現也不同。這些方法如下:
/ | 拋異常 | 特定值 | 阻塞 | 超時 |
---|---|---|---|---|
插入 | add(o) | offer(o) | put(o) | offer(o, timeout, TimeUnit) |
移出 | remove() | poll() | take() | poll(timeout, timeunit) |
檢查 | element() | peek() |
- 四組不同的行為方式解釋:
- 拋異常:如果試圖的操作無法立即執行,拋一個異常。
- 特定值:如果試圖的操作無法立即執行,返回一個特定的值(常常是 true / false)。
- 阻塞:如果試圖的操作無法立即執行,該方法調用將會發生阻塞,直到能夠執行。
- 超時:如果試圖的操作無法立即執行,該方法調用將會發生阻塞,直到能夠執行,但等待時間不會超過給定值。返回一個特定值以告知該操作是否成功(典型的是 true / false)。
- 無法向一個 BlockingQueue 中插入 null。如果你試圖插入 null,BlockingQueue 將會拋出一個 NullPointerException。
方法詳解(只分析 ArrayBlockingQueue 這一種實現,其他的類似)
-
插入數據方法:add(o)、offer(o)、put(o)、offer(o, timeout, TimeUnit)
-
void put(E e) throws InterruptedException;
:隊列的容量已滿時,線程會一直阻塞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); // 會在插入方法中調用 notEmpty.signal(); 喚醒阻塞的take方法線程 } finally { lock.unlock(); } }
-
boolean offer(E e);
試著往隊列中插入值,不管隊列滿沒滿,都會返回結果public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) // 當隊列已滿時,直接返回false return false; else { insert(e); return true; } } finally { lock.unlock(); } }
-
boolean add(E e);
add方法實際上是AbstractQueue
中的方法,里面的實現是先調用上述的 offer 方法,當 offer 方法返回為 true 是,直接返回true, 如果是返回 false 是,直接拋出異常IllegalStateException Queue full
public boolean add(E e) { if (offer(e)) // 直接調用 offer 方法 return true; else // 如果 offer 返回為 false, 那么會拋出 Queue full 異常 throw new IllegalStateException("Queue full"); }
-
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
跟offer(o)
方法相似,會先判斷 隊列是否已滿,如果隊列已滿,會循環等待傳入的時間,當超過設置的時間后,還是無空閑位置。會直接返回一個false。public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { checkNotNull(e); long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) { // 輪詢判斷隊列是否已滿 if (nanos <= 0) // 如果隊列滿了,判斷設置的等待時間是否超時了,超時了直接返回false return false; nanos = notFull.awaitNanos(nanos); // 沒超時,調用計算時間方法, 進入下一個循環中繼續判斷 } insert(e); // 在輪詢等待的時間內,有空閑就立刻插入數據 return true; } finally { lock.unlock(); } }
-
-
移出數據的方法:remove()、 remove(o)、poll()、take()、 poll(timeout, TimeUnit)
-
E poll();
獲取隊列頭的數據,并刪除該數據(將該位置數據置為null)。如果隊列已經空了,那么直接返回 nullpublic E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : extract(); // 判斷隊列是否為空,為空直接返回null } finally { lock.unlock(); } } // 如果隊列不為空,調用extract() 方法, 這個方法調用必須獲取鎖 /** * Extracts element at current take position, advances, and signals. * Call only when holding lock. */ private E extract() { final Object[] items = this.items; E x = this.<E>cast(items[takeIndex]); // 獲取當前位置的元素(隊列頭) items[takeIndex] = null; // 然后把該位置置為null takeIndex = inc(takeIndex); // 把索引向前推進 --count; // 元素容量計數也減少 notFull.signal(); // 通知其他線程,可以進行put操作了 return x; // 返回結果 }
-
E poll(long timeout, TimeUnit unit) throws InterruptedException;
判斷隊列是否為空,如果為空等待設置的時間,等待時間超過設置的時間。直接返回null。public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) { // 判斷隊列是否為空 if (nanos <= 0) // 如果為空,判斷是否等待時間已耗盡 return null; // 耗盡,直接返回null nanos = notEmpty.awaitNanos(nanos); // 沒耗盡,調用計算時間方法進入下一個循環 } return extract(); // 隊列不為空,直接調用獲取數據的方法 } finally { lock.unlock(); } }
-
E take() throws InterruptedException;
take 方法,如果隊列為空,會進入阻塞狀態public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) //當隊列中沒有元素時,會進入阻塞狀態等待喚醒 notEmpty.await(); return extract(); } finally { lock.unlock(); } }
-
E remove();
實際是調用 poll() 方法獲取隊列頭對象并刪除,如果返回的值不為null,那么久直接返回。如果為null 就拋出NoSuchElementException
public E remove() { E x = poll(); // 移出隊列頭對象 if (x != null) return x; else throw new NoSuchElementException(); // 如果返回的值是null 直接拋出異常信息 }
boolean remove(Object o);
該方法不管有沒有獲取到隊列頭對象,都會返回一個結果值。不會拋出異常信息。public boolean remove(Object o) { if (o == null) return false; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) { //遍歷隊列 if (o.equals(items[i])) { // 判斷對象是否相等 removeAt(i); return true; } } return false; // 如果沒有相等的對象返回false } finally { lock.unlock(); } } void removeAt(int i) { final Object[] items = this.items; // if removing front item, just advance if (i == takeIndex) { // 如果需要移出的對象就是當前的隊列頭,直接置為null且索引后移 items[takeIndex] = null; takeIndex = inc(takeIndex); } else { // slide over all others up through putIndex. for (;;) { int nexti = inc(i); // 需要移出元素索引的后一個為 nexti if (nexti != putIndex) { // 當下一個索引不等于下一個添加元素的位置, 首次移出時ArrayBlockingQueue 的 putIndex 為0, 隊列頭。移除后為移出的索引位置 items[i] = items[nexti]; // 后一個元素覆蓋前一個元素 i = nexti; // 繼續向后推進 } else { items[i] = null; // 把最后一個元素置為null putIndex = i; // 最新添加元素的位置 putIndex 為 i break; } } } --count; notFull.signal(); }
-
-
檢查數據的方法: element()、peek()
-
E peek();
返回隊列頭的元素,但是不刪除。如果隊列為空返回nullpublic E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : itemAt(takeIndex); // 返回隊列頭位置元素 } finally { lock.unlock(); } } final E itemAt(int i) { return this.<E>cast(items[i]); // 直接返回隊列頭元素,不做刪除操作 }
-
E element();
返回隊列頭元素,如果為 null 拋出NoSuchElementException
異常, 調用的實際上是peek()
方法。public E element() { E x = peek(); if (x != null) return x; else throw new NoSuchElementException(); }
-
-
其他方法: contains(o)、remainingCapacity()、drainTo(Collection<? super E> c)、drainTo(Collection<? super E> c, int maxElements);
public boolean contains(Object o);
使用迭代器遍歷元素判斷是否包含Object,返回true or falseint remainingCapacity();
返回剩余容量大小,如果是ArrayBlockingQueue 那么就是數組容量的大小減去已經存在元素的個數值。int drainTo(Collection<? super E> c);
把隊列全部轉換成 Collection 類,并且清空隊列自身。-
int drainTo(Collection<? super E> c, int maxElements);
從隊列頭部開始,轉換并清空 maxElements 個元素成 Collection 類?
待后續的其他隊列實現類粗略講解