ArrayBlockingQueue
功能
全名
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable
簡述
由數組支持的有界阻塞隊列。這個隊列對元素FIFO(先進先出)排序。隊列的頭是隊列中存在時間最長的元素。隊列的尾部是隊列中存在時間最短的元素。新元素插入到隊列的尾部,隊列檢索操作獲取隊列頭部的元素。
這是一個典型的“有界緩衝區”,其中大小固定的數組保存由生產者插入並由消費者提取的元素。一旦創建,容量就不能更改。試圖將一個元素放入一個滿隊列將導致操作阻塞;嘗試從空隊列中獲取元素也會發生阻塞。
該類支持一個可選的公平性策略,用於對正在等待的生產者和消費者線程進行排序。默認情況下,不保證這種順序。但是,將公平性設置爲true的隊列將按FIFO順序授予線程訪問權。公平性通常會降低吞吐量,但會降低可變性並避免飢餓。
方法
// 在不超過隊列容量的情況下立即插入指定的元素,成功後返回true,如果隊列已滿則拋出IllegalStateException。 public boolean add(E e) // 在不超過隊列容量的情況下立即在隊列末尾插入指定的元素,如果成功則返回true,如果隊列已滿則返回false。此方法通常比add(E)方法更好,後者插入元素失敗只能拋出異常。 public boolean offer(E e) // 將指定的元素插入到此隊列的末尾,如果隊列已滿則等待直到有可用的空間。 public void put(E e) throws InterruptedException // 將指定的元素插入到此隊列的末尾,如果隊列已滿,則在指定的超時時間之內等待空間可用,超時返回false。 public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException // 檢索並刪除此隊列的頭,如果此隊列爲空,則返回null。 public E poll() // 檢索並刪除此隊列的頭,如有必要則等待,直到某個元素可用爲止。 public E take() throws InterruptedException // 檢索並刪除此隊列的頭,如果有必要則在指定的等待時間之內等待元素可用,超時返回null。 public E poll(long timeout, TimeUnit unit) throws InterruptedException // 檢索但不刪除此隊列的頭,或在此隊列爲空時返回null。 public E peek() // 返回此隊列中的元素數量。 public int size() // 返回此隊列在理想情況下(在沒有內存或資源約束的情況下)可以不阻塞地接受的新元素的數量。它總是等於這個隊列的初始容量減去這個隊列的當前大小。 public int remainingCapacity() // 如果指定元素存在,則從此隊列中移除該元素的單個實例。更正式地說,如果隊列中包含一個或多個這樣的元素,則只刪除匹配到的第一個元素 public boolean remove(Object o) // 如果此隊列包含至少一個指定的元素,則返回true。 public boolean contains(Object o) // 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列。返回的數組將是“安全的”,因爲此隊列不維護對它的引用。 public Object[] toArray() // 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列;返回數組的運行時類型是指定數組的運行時類型。 public <T> T[] toArray(T[] a) // 返回此集合的字符串表示形式。 public String toString() // 刪除此隊列中的所有元素。此調用返回後,隊列將爲空。 public void clear() // 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。 public int drainTo(Collection<? super E> c) // 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上 public int drainTo(Collection<? super E> c, int maxElements) // 按適當的順序返回此隊列中元素的迭代器。元素將按從第一個(head)到最後一個(tail)的順序返回。返回的迭代器是弱一致的。 public Iterator<E> iterator() // 返回該隊列中元素的Spliterator。返回的spliterator是弱一致的。 public Spliterator<E> spliterator()
原理
/** 隊列裏元素數量 */ int count; /** 存儲結構 */ final Object[] items; /** 爲下一次執行 take, poll, peek or remove 操作提供的index */ int takeIndex; /** 爲下一次執行 put, offer, or add 操作提供的index */ int putIndex; /** 當隊列爲空時獲取等待 */ private final Condition notEmpty; /** 當隊列滿時插入等待 */ private final Condition notFull;
offer和put
public boolean offer(E e) { // 不允許null值 checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { // 隊列裏元素的數量等於隊列長度(隊列滿,插入返回false) if (count == items.length) return false; else { // 插入 enqueue(e); return true; } } finally { lock.unlock(); } } private void enqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; // 插入數據 items[putIndex] = x; // 如果putIndex+1等於隊列長度,將putIndex置爲0,爲下一輪插入做準備 if (++putIndex == items.length) putIndex = 0; // 隊列元素數量+1 count++; // 通知讀取線程可以讀取了 notEmpty.signal(); } public void put(E e) throws InterruptedException { // null值檢查 checkNotNull(e); final ReentrantLock lock = this.lock; // 獲取一個優先考慮中斷的鎖 lock.lockInterruptibly(); try { // 只要隊列滿了,則插入線程阻塞,直到有可用空間 while (count == items.length) notFull.await(); enqueue(e); } finally { lock.unlock(); } } public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { // null值檢查 checkNotNull(e); long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { // 在隊列滿了之後進入循環 while (count == items.length) { // 如果小於等於0則超時,返回false if (nanos <= 0) return false; // awaitNanos方法:使當前線程等待,直到收到signal或被中斷,或指定的等待時間過期。 // 如果在時間之內收到了signal,則返回timeout - 已等待的時間;如果超時了,則返回0或者負數 nanos = notFull.awaitNanos(nanos); } enqueue(e); return true; } finally { lock.unlock(); } }
poll和take
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { // 如果隊列空了,直接返回null return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; // 臨時變量存儲返回結果 items[takeIndex] = null; // 當前位置置爲空 // 如果takeIndex+1等於隊列長度,takeIndex置爲0,爲下一輪讀取做準備 if (++takeIndex == items.length) takeIndex = 0; // 隊列元素數量-1 count--; // 如果當前迭代器不爲空,迭代器也要做出更新 if (itrs != null) itrs.elementDequeued(); // 通知寫入線程寫入數據 notFull.signal(); return x; } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { // 如果隊列爲空則一直等待,直到有可用元素 while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } 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) { // 如果小於等於0則超時,返回null if (nanos <= 0) return null; // awaitNanos方法:使當前線程等待,直到收到signal或被中斷,或指定的等待時間過期。 // 如果在時間之內收到了signal,則返回timeout - 已等待的時間;如果超時了,則返回0或者負數 nanos = notEmpty.awaitNanos(nanos); } return dequeue(); } finally { lock.unlock(); } }
從上面的進隊出隊我們可以知道大概的流程,首先內部存儲結構是一個定長數組,初始情況putIndex和takeIndex都爲0
當執行插入後,putIndex+1,爲下一次插入做準備。當putIndex移動到最後的時候,+1正好等於隊列最大長度,這個時候要將它置爲初始狀態也就是0。takeIndex同樣的道理。
那麼爲什麼要這麼做,我已經知道了:避免了插入刪除導致的元素移動!同時也保證了隊列的性質:先進先出。
remove方法
public boolean remove(Object o) { if (o == null) return false; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { if (count > 0) { // final變量,不可變,下面循環做條件 final int putIndex = this.putIndex; // 初始從takeIndex所指的位置開始 int i = takeIndex; do { // 找到了,執行移除代碼 if (o.equals(items[i])) { removeAt(i); return true; } // 到末尾了,從頭開始找 if (++i == items.length) i = 0; } while (i != putIndex); // 只要不是putIndex所指,繼續找(因爲putIndex所指的位置是null) } return false; } finally { lock.unlock(); } } void removeAt(final int removeIndex) { // assert lock.getHoldCount() == 1; // assert items[removeIndex] != null; // assert removeIndex >= 0 && removeIndex < items.length; final Object[] items = this.items; // 如果移除的位置和takeIndex指向一致,相當於執行了一次出隊操作,僅僅改變takeIndex即可。 if (removeIndex == takeIndex) { // removing front item; just advance items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); } else { // an "interior" remove // slide over all others up through putIndex. final int putIndex = this.putIndex; for (int i = removeIndex;;) { int next = i + 1; if (next == items.length) next = 0; // 從移除位置開始,後面的向前複製 if (next != putIndex) { items[i] = items[next]; i = next; } else { // 直到當前位置的下一個是putIndex,將當前位置置null,putIndex指向當前位置,跳出循環 items[i] = null; this.putIndex = i; break; } } // 元素數量-1 count--; // 同步迭代器 if (itrs != null) itrs.removedAt(removeIndex); } notFull.signal(); }
remove方法,移除元素的位置不是takeIndex的時候,移除過程是下面這樣
最後看一下drainTo
// 把隊列元素刪除,並將元素添加到集合c中 public int drainTo(Collection<? super E> c) { // 這裏可以看到,默認刪除數量爲Integer.MAX_VALUE return drainTo(c, Integer.MAX_VALUE); } public int drainTo(Collection<? super E> c, int maxElements) { checkNotNull(c); if (c == this) throw new IllegalArgumentException(); if (maxElements <= 0) return 0; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { // 從給定的數量和隊列元素數量選出最小的一個(也就是說,無論你設置的數量爲多大,最多也就是把整個隊列清空,不然訪問不存在的位置會出異常) int n = Math.min(maxElements, count); // 從takeIndex開始 int take = takeIndex; int i = 0; try { // 操作n個元素 while (i < n) { @SuppressWarnings("unchecked") E x = (E) items[take]; // 添加到c中 c.add(x); // 原隊列刪除 items[take] = null; // take向後移動;如果到了隊尾,從頭開始 if (++take == items.length) take = 0; i++; } return n; } finally { // Restore invariants even if c.add() threw if (i > 0) { count -= i; // 剩餘隊列元素數量 takeIndex = take; // 重置takeIndex if (itrs != null) { // 如果隊列空了,迭代器也要做出同步 if (count == 0) itrs.queueIsEmpty(); else if (i > take) // 當takeIndex變爲0時調用。 itrs.takeIndexWrapped(); } // 通知等待的插入線程 for (; i > 0 && lock.hasWaiters(notFull); i--) notFull.signal(); } } } finally { lock.unlock(); } }
優缺點
優點:有界隊列,避免了內存濫用;內部插入刪除實現避免了元素移動,時間複雜度爲O(1),高效率;線程安全;
缺點:我覺得這個在特定場景已經很完美了。
如果你想在進隊出隊時不滿足條件立即返回,則直接用offer和poll;如果你希望等待,則用put和take;如果你希望等一會,不行再返回,則用offer(E e, long timeout, TimeUnit unit)和poll(long timeout, TimeUnit unit)。
remove方法儘量不用吧,因爲它在刪除之後要去判斷調整putIndex或者takeIndex。
DelayQueue
功能
全名
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E>
簡述
延遲元素的無界阻塞隊列,其中一個元素只能在其延遲過期後才能被獲取。
隊列的頭是延遲元素。如果沒有過期,就沒有head, poll將返回null。
當元素的getDelay(TimeUnit.NANOSECONDS)方法返回一個小於或等於零的值時,就會發生過期。
未過期的元素不能使用take或poll刪除,它們被視爲正常元素。根據定義,它的元素必須是Delayed接口的實現。
方法
// 將指定的元素插入此延遲隊列。 public boolean add(E e) // 將指定的元素插入此延遲隊列 public boolean offer(E e) // 將指定的元素插入此延遲隊列。因爲隊列是無界的,所以這個方法永遠不會阻塞。 public void put(E e) // 將指定的元素插入此延遲隊列。因爲隊列是無界的,所以這個方法永遠不會阻塞。 public boolean offer(E e, long timeout, TimeUnit unit) // 檢索並刪除此隊列的頭,如果此隊列沒有過期的元素,則返回null。 public E poll() // 檢索並刪除此隊列的頭,如有必要,將一直等待,直到此隊列上有一個過期的元素可用爲止。 public E take() throws InterruptedException // 檢索並刪除此隊列的頭,如有必要,將一直等待,直到此隊列中具有過期延遲的元素可用,或指定的等待時間過期。 public E poll(long timeout, TimeUnit unit) throws InterruptedException // 檢索但不刪除此隊列的頭,或在此隊列爲空時返回null。與poll方法不同,如果隊列中沒有可用的過期元素,此方法將返回下一個將要過期的元素(如果存在的話)。 public E peek() // 返回此集合中的元素數。如果此集合元素數大於Integer.MAX_VALUE,也只返回Integer.MAX_VALUE public int size() // 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。 public int drainTo(Collection<? super E> c) // 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上 public int drainTo(Collection<? super E> c, int maxElements) // 刪除此延遲隊列中的所有元素。此調用返回後,隊列將爲空。不等待未過期的元素;它們只是從隊列中被丟棄。 public void clear() // 總是返回Integer.MAX_VALUE,因爲延遲隊列沒有容量限制。 public int remainingCapacity() // 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列。返回的數組將是“安全的”,因爲此隊列不維護對它的引用。 public Object[] toArray() // 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列;返回數組的運行時類型是指定數組的運行時類型。 public <T> T[] toArray(T[] a) // 從此隊列中刪除指定元素的單個實例(如果存在),無論它是否已過期。 public boolean remove(Object o) // 返回此隊列中所有元素(過期和未過期)的迭代器。迭代器不會以任何特定的順序返回元素。返回的迭代器是弱一致的。 public Iterator<E> iterator()
原理
成員變量
private final transient ReentrantLock lock = new ReentrantLock(); // 延遲隊列的內部存儲結構是優先級隊列 [praɪˈɒrəti][kjuː] private final PriorityQueue<E> q = new PriorityQueue<E>(); private Thread leader = null; // 當一個新的元素在隊列的最前面可用,或者一個新線程需要成爲leader時,就會發出條件信號。 private final Condition available = lock.newCondition();
offer和put
public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { // 入隊 q.offer(e); if (q.peek() == e) { leader = null; // 如果隊頭元素是當前元素(新添加的元素已經過期),則通知等待線程 available.signal(); } return true; } finally { lock.unlock(); } } // 實際調用的是offer public void put(E e) { offer(e); } // 不僅僅調用的offer,並且timeout參數,unit參數都沒有用到 public boolean offer(E e, long timeout, TimeUnit unit) { return offer(e); } // java.util.PriorityQueue#offer public boolean offer(E e) { if (e == null) throw new NullPointerException(); // 內部存儲結構修改次數 modCount++; int i = size; // 如果隊列元素數量大於等於隊列長度,則擴容 if (i >= queue.length) grow(i + 1); // 隊列元素數量+1 size = i + 1; if (i == 0) // 如果隊列爲空,直接放在第一位 queue[0] = e; else // 隊列不爲空,在i的位置插入e。【這裏面有個排序的過程,根據我們定義的排序規則把最早過期的排在內部數組第一位,保證隊頭是最早過期的元素】 siftUp(i, e); return true; } // java.util.PriorityQueue#grow private void grow(int minCapacity) { int oldCapacity = queue.length; // 如果老容量小於64,則新容量 = 2*老容量+2;如果老容量大於等於64,則新容量 = 1.5*老容量 int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1)); // overflow-conscious code if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); queue = Arrays.copyOf(queue, newCapacity); } // java.util.PriorityQueue#peek public E peek() { // 不爲空,則返回隊列第一個元素(準確來說是內部數組第一個) return (size == 0) ? null : (E) queue[0]; }
這裏可以看出,延遲隊列的內部實現是優先級隊列,優先級隊列的內部實現是一個數組,擴容的時候新容量與ArrayList的有一丟丟不一樣。因爲可以擴容,所以延時隊列是個無界隊列。
poll和take
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { // 獲取隊頭 E first = q.peek(); // 如果隊頭爲空或者未過期,直接返回空 if (first == null || first.getDelay(NANOSECONDS) > 0) return null; else // 否則出隊並返回 return q.poll(); } finally { lock.unlock(); } } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { E first = q.peek(); // 如果獲取不到,則等待 if (first == null) available.await(); else { // 獲取剩餘過期時間 long delay = first.getDelay(NANOSECONDS); // 滿足過期條件,出隊並返回 if (delay <= 0) return q.poll(); // 當線程等待,不保留這個引用 first = null; // don't retain ref while waiting if (leader != null) available.await(); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { // 等待剩餘過期時間那麼長的時間 available.awaitNanos(delay); } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } } public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { E first = q.peek(); // 沒有獲取到有效元素 if (first == null) { // timeout無效,返回空 if (nanos <= 0) return null; else // 否則等待timeout那麼長的時間 nanos = available.awaitNanos(nanos); } else { // 獲取剩餘過期時間 long delay = first.getDelay(NANOSECONDS); if (delay <= 0) // 已經過期,順利出隊返回 return q.poll(); // timeout無效,返回空 if (nanos <= 0) return null; first = null; // don't retain ref while waiting if (nanos < delay || leader != null) // 如果timeout時間小於剩餘過期時間,等待timeout那麼長的時間 nanos = available.awaitNanos(nanos); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { // 否則等待剩餘過期時間那麼長的時間 long timeLeft = available.awaitNanos(delay); nanos -= delay - timeLeft; } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } }
小例子:
import java.util.concurrent.*; public class Main { public static void main(String[] args) throws Exception { DelayQueue<MyDelay> list = new DelayQueue<>(); list.offer(new MyDelay(1000, "A")); list.offer(new MyDelay(2000, "B")); list.offer(new MyDelay(3000, "C")); for (int i = 0; i < 3; i++){ System.out.println(list.take().toString()); } } } class MyDelay implements Delayed { private long expireTime; private String name; public MyDelay(long expireTime, String name) { this.expireTime = System.currentTimeMillis() + expireTime; this.name = name; } @Override public long getDelay(TimeUnit unit) { return expireTime - System.currentTimeMillis(); } @Override public int compareTo(Delayed o) { return Long.compare(expireTime, ((MyDelay)o).expireTime); } @Override public String toString() { return "MyDelay{" + "expireTime=" + expireTime + ", name='" + name + '\'' + '}'; } }
運行結果:按照過期順序輸出
優缺點
內部是依賴優先級隊列實現的,也可以說,延時隊列是優先級隊列的一個特例(按照時間過期順序排序)。因爲是無界隊列,插入不會阻塞;由於過期時間的限制,poll和take會阻塞。
使用場景:
1. 訂單超30分鐘未付款即關閉訂單。
2. 定時任務:延時隊列保存要執行的任務,一旦獲取到即執行。
3. 服務器中的客戶端連接空閒一段時間之後就需要關閉。
爲什麼用Leader-Follower模式?
官方說法:Leader爲等待隊列頭部的元素的線程。 Leader-Follower模式的這種變體可以最小化不必要的等待時間。在從take()或poll(…)返回之前,領導線程必須向其他線程發出信號,除非其他線程在此期間成爲領導線程。
模式原理:一開始創建一個線程池,選取一個當做Leader監聽任務,其它線程等待;當Leader拿到了任務,即釋放自己的權利,然後從等待線程中選取一個作爲新的Leader去監聽任務,自己則去執行拿到的任務,執行完任務進入等待狀態。由於接受任務和執行都是同一個線程,則避免了上下文切換的開銷。
LinkedBlockingQueue
功能
全名
public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable
簡述
基於鏈表的有界阻塞隊列。這個隊列對元素FIFO(先進先出)排序。隊列的頭是隊列中存在時間最長的元素。隊列的尾部是隊列中時間最短的元素。
新元素插入到隊列的尾部,隊列檢索操作獲取隊列頭部的元素。鏈表隊列通常比基於數組的隊列具有更高的吞吐量,但在大多數併發應用程序中,其性能的可預測性較差。
構造函數裏的容量參數用作防止隊列過度擴展的一種方法。容量(如果未指定)等於Integer.MAX_VALUE。這裏可以得知,LinkedBlockingQueue的有界和無界是靈活的:如果你需要有界,則必須在構造方法指定容量;如果不指定,容量等於Integer.MAX_VALUE,和無界沒有什麼區別了。
方法
// 返回此隊列中的元素數量。 public int size() // 返回此隊列在理想情況下(在沒有內存或資源約束的情況下)可以不阻塞地接受新元素的數量。它總是等於這個隊列的初始容量減去這個隊列的當前大小。 public int remainingCapacity() // 將指定的元素插入到此隊列的末尾,如果需要,則等待空間可用。 public void put(E e) throws InterruptedException // 將指定的元素插入到此隊列的末尾,如有必要,將等待指定的等待時間,直到空間可用爲止。超時則返回false。 public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException // 在不超過隊列容量的情況下立即在隊列末尾插入指定的元素,如果成功則返回true,如果隊列已滿則返回false。當使用容量受限的隊列時,此方法通常比add方法更好,後者插入失敗僅拋出異常。 public boolean offer(E e) // 檢索並刪除此隊列的頭,如有必要則等待,直到某個元素可用爲止。 public E take() throws InterruptedException // 檢索並刪除此隊列的頭,如有必要,將等待指定的等待時間,直到元素可用。超時返回null。 public E poll(long timeout, TimeUnit unit) throws InterruptedException // 檢索並刪除此隊列的頭,如果此隊列爲空,則返回null。 public E poll() // 檢索並刪除此隊列的頭,如果此隊列爲空,則返回null。 public E peek() // 如果指定元素存在,則從此隊列中移除該元素的單個實例。更正式地說,如果隊列中包含一個或多個這樣的元素,則只刪除第一個匹配到的元素。 public boolean remove(Object o) // 如果此隊列包含至少一個指定的元素,則返回true。 public boolean contains(Object o) // 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列。返回的數組將是“安全的”,因爲此隊列不維護對它的引用。 public Object[] toArray() // 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列;返回數組的運行時類型是指定數組的運行時類型。 public <T> T[] toArray(T[] a) // 返回此集合的字符串表示形式。 public String toString() // 刪除此隊列中的所有元素。此調用返回後,隊列將爲空。 public void clear() // 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。 public int drainTo(Collection<? super E> c) // 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上 public int drainTo(Collection<? super E> c, int maxElements) // 按適當的順序返回此隊列中元素的迭代器。元素將按從第一個(head)到最後一個(tail)的順序返回。返回的迭代器是弱一致的。 public Iterator<E> iterator() // 返回該隊列中元素的Spliterator。返回的spliterator是弱一致的。 public Spliterator<E> spliterator()
原理
成員變量
/** 隊列容量,默認Integer.MAX_VALUE */ private final int capacity; /** 當前隊列元素數量 */ private final AtomicInteger count = new AtomicInteger(); /** * 鏈表的head */ transient Node<E> head; /** * 鏈表的tail */ private transient Node<E> last; /** 由take, poll方法持有的鎖 */ private final ReentrantLock takeLock = new ReentrantLock(); /** 當take的時候,如果隊列爲空,則等待 */ private final Condition notEmpty = takeLock.newCondition(); /** 由put, offer方法持有的鎖 */ private final ReentrantLock putLock = new ReentrantLock(); /** 當put的時候,如果隊列爲空,則等待 */ private final Condition notFull = putLock.newCondition();
put和offer
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>(e); // 拿到putLock final ReentrantLock putLock = this.putLock; // 拿到計數器 final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { /* * Note that count is used in wait guard even though it is * not protected by lock. This works because count can * only decrease at this point (all other puts are shut * out by lock), and we (or some other waiting put) are * signalled if it ever changes from capacity. Similarly * for all other uses of count in other wait guards. */ // 如果元素數量等於容量,也就是隊列滿了,則阻塞 while (count.get() == capacity) { notFull.await(); } // 入隊 enqueue(node); // 獲取並加1;注意這裏的c = 加1之前的值 c = count.getAndIncrement(); // 通知其它線程繼續入隊 if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } // 因爲c的值是+1之前的,所以在此情況下:實際上隊列元素數量爲1,則通知等待中的take線程來拿取 if (c == 0) signalNotEmpty(); } public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { if (e == null) throw new NullPointerException(); long nanos = unit.toNanos(timeout); int c = -1; final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { // 如果隊列滿了,則等待指定時間;如果指定時間內有可用空間,則繼續向下執行;如果超時,則返回false while (count.get() == capacity) { if (nanos <= 0) return false; nanos = notFull.awaitNanos(nanos); } // 入隊 enqueue(new Node<E>(e)); // 先獲取元素數量, 然後+1 c = count.getAndIncrement(); // 入隊之後還有可用空間,則通知其它等待線程,繼續入隊 if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return true; } public boolean offer(E e) { if (e == null) throw new NullPointerException(); final AtomicInteger count = this.count; if (count.get() == capacity) return false; int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; // 注意這裏不是lockInterruptibly putLock.lock(); try { // 如果有可用空間,則入隊;否則返回false(c= -1) if (count.get() < capacity) { enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return c >= 0; }
take和poll
public E take() throws InterruptedException { E x; int c = -1; // 拿到計數器 final AtomicInteger count = this.count; // 拿到takeLock final ReentrantLock takeLock = this.takeLock; // 優先響應中斷 takeLock.lockInterruptibly(); try { // 如果隊列爲空,則等待,直到有可用元素 while (count.get() == 0) { notEmpty.await(); } // 出隊 x = dequeue(); // 獲取並減1;注意這裏的c = 減1之前的值 c = count.getAndDecrement(); // 通知其它等待線程,繼續出隊 if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } // 因爲c的值是-1之前的,所以在此情況下:隊列裏面元素數量 = 容量大小 - 1,則通知等待中的添加線程添加 if (c == capacity) signalNotFull(); return x; } public E poll(long timeout, TimeUnit unit) throws InterruptedException { E x = null; int c = -1; long nanos = unit.toNanos(timeout); final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { // 如果隊列爲空,則等待指定時間;如果指定時間內有可用元素,則繼續向下執行;如果超時,則返回null while (count.get() == 0) { if (nanos <= 0) return null; nanos = notEmpty.awaitNanos(nanos); } // 出隊 x = dequeue(); // 先獲取元素數量,然後-1 c = count.getAndDecrement(); // 通知其它等待中的take線程 if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } // 通知等待中的添加線程添加 if (c == capacity) signalNotFull(); return x; } public E poll() { final AtomicInteger count = this.count; if (count.get() == 0) return null; E x = null; int c = -1; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { // 如果有可用元素,出隊;否則返回null if (count.get() > 0) { x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
peek
public E peek() { // 隊列爲空,返回null if (count.get() == 0) return null; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { // 返回head指向的數據 Node<E> first = head.next; if (first == null) return null; else return first.item; } finally { takeLock.unlock(); } }
enqueue和dequeue
// 入隊操作,就是把新結點添加到隊列的最後 private void enqueue(Node<E> node) { // assert putLock.isHeldByCurrentThread(); // assert last.next == null; // 尾指針的next指向新結點,當前尾指針指向尾指針的next; last = last.next = node; } // 出隊操作,就是把隊頭移除 private E dequeue() { // assert takeLock.isHeldByCurrentThread(); // assert head.item == null; // 當前隊頭 Node<E> h = head; // 下一任隊頭(head的next節點) Node<E> first = h.next; h.next = h; // help GC // 任命新的隊頭 head = first; // 老數據 E x = first.item; first.item = null; // 將老數據返回 return x; }
通過這個出隊操作,可以得知,內部的鏈表的head指針不存儲任何數據,僅僅是標記的作用(帶頭結點的鏈表,雖然多佔了一個結點的空間,但是處理邏輯變簡單了)
clear
public void clear() { // 拿到兩把鎖 fullyLock(); try { // 遍歷鏈表刪除 for (Node<E> p, h = head; (p = h.next) != null; h = p) { h.next = h; p.item = null; } // 最後首尾合一 head = last; // assert head.item == null && head.next == null; if (count.getAndSet(0) == capacity) notFull.signal(); } finally { fullyUnlock(); } }
優缺點
內部結構是個鏈表,新增和刪除操作都涉及結點的創建(封裝成Node)和刪除(Node的回收),相比ArrayBlockingQueue是直接在數組位置進行數據的賦值與刪除,開銷大了一些。
相比ArrayBlockingQueue只用了一把鎖,LinkedBlockingQueue使用了takeLock和putLock兩把鎖,分別用於阻塞隊列的讀寫線程,也就是說,讀線程和寫線程可以同時運行,在多線程高併發場景,可以有更高的吞吐量。
因爲隊列的讀寫分別在頭部和尾部,相互競爭的機率較小,所以用雙鎖可以實現更高的吞吐量;而ArrayBlockingQueue,只有一個數組,也可以用雙鎖,但是代碼可就複雜了。