LinkedTransferQueue
功能
全名
public class LinkedTransferQueue<E> extends AbstractQueue<E> implements TransferQueue<E>, Serializable
簡述
基於鏈表的的無界隊列。隊列的頭是某個生產者在隊列中停留時間最長的元素。隊列的尾部是某個生產者在隊列中時間最短的元素。
注意,與大多數集合不同,size方法不是一個常量時間操作。由於這些隊列的異步性,確定當前元素的數量需要遍歷元素,因此如果在遍歷期間修改此集合,可能會報告不準確的結果。
此外,批量操作addAll、removeAll、retainAll、containsAll、equals和toArray不能保證自動執行。例如,與addAll操作併發操作的迭代器可能只查看一些添加的元素。
從JDK1.7被引入,它既有SynchronousQueue的“交換”特性(還比SynchronousQueue多了用於存儲的空間),也具有阻塞隊列的“阻塞”特性(由於不加鎖,性能比LinkedBlockingQueue要好得多)
方法
// 返回該隊列中元素的Spliterator。返回的spliterator是弱一致的。 public Spliterator<E> spliterator() // 將指定的元素插入到此隊列的末尾。因爲隊列是無界的,所以這個方法永遠不會阻塞。 public void put(E e) // 將指定的元素插入到此隊列的末尾。因爲隊列是無界的,所以這個方法永遠不會阻塞或返回false。後兩個參數不會被用到。 public boolean offer(E e, long timeout, TimeUnit unit) // 將指定的元素插入到此隊列的末尾。因爲隊列是無界的,所以這個方法永遠不會返回false。 public boolean offer(E e) // 將指定的元素插入到此隊列的末尾。因爲隊列是無界的,所以這個方法永遠不會拋出IllegalStateException或返回false。 public boolean add(E e) // 如果可能,立即將元素傳輸給等待的使用者。更準確地說,如果存在一個消費者已經在等待接收它(take()方法或超時的poll方法),則立即傳輸指定的元素,否則返回false而不排隊該元素。 public boolean tryTransfer(E e) // 將元素傳輸給使用者,如有必要則等待。更準確地說,如果存在一個消費者已經在等待接收指定的元素(take()方法或超時的poll方法),則立即傳輸指定的元素,否則將在此隊列的末尾插入指定的元素並等待,直到消費者接收到該元素。 public void transfer(E e) throws InterruptedException // 如果可以在超時之前將元素傳輸給使用者,則將其傳輸給使用者。更準確地說,如果存在一個消費者已經等待則立即轉移指定的元素;否則在這個隊列的尾部插入指定元素並等待,直到該元素被一個消費者接收到,如果元素可以被轉移之前超時則返回false。 public boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException // 檢索並刪除此隊列的頭,如有必要則等待,直到某個元素可用爲止。 public E take() throws InterruptedException // 檢索並刪除此隊列的頭,如果有必要,則需要等待指定的等待時間。 public E poll(long timeout, TimeUnit unit) throws InterruptedException // 檢索並刪除此隊列的頭,如果此隊列爲空,則返回null。 public E poll() // 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。 public int drainTo(Collection<? super E> c) // 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上 public int drainTo(Collection<? super E> c, int maxElements) // 按適當的順序返回此隊列中元素的迭代器。元素將按從第一個(head)到最後一個(tail)的順序返回。返回的迭代器是弱一致的。 public Iterator<E> iterator() // 檢索但不刪除此隊列的頭,在此隊列爲空時返回null。 public E peek() // 如果此隊列不包含元素,則返回true。 public boolean isEmpty() // 如果至少有一個消費者在通過BlockingQueue.take()或超時的poll()方法等待接收元素,則返回true。 public boolean hasWaitingConsumer() // 返回此隊列中的元素數量。如果此隊列大於Integer.MAX_VALUE個元素,則返回Integer.MAX_VALUE。注意,與大多數集合不同,此方法不是常量時間操作。由於這些隊列的異步性,確定當前元素的數量需要O(n)遍歷。 public int size() // 返回通過BlockingQueue.take()或超時的poll()方法等待接收元素的消費者數量的估計值。返回值是事件瞬間狀態的近似值,如果消費者已經完成或放棄等待,則返回值可能不準確。該值可能對監視和啓發有用,但對同步控制沒用。此方法的實現可能比TransferQueue.hasWaitingConsumer()方法的實現慢得多。 public int getWaitingConsumerCount() // 如果指定元素存在,則從此隊列中移除第一個匹配的元素。 public boolean remove(Object o) // 如果此隊列包含至少一個指定的元素,則返回true。 public boolean contains(Object o) // 總是返回 Integer.MAX_VALUE ,因爲這是無界隊列 public int remainingCapacity()
原理
凡是涉及到添加的方法:put、offer、offer(timeout)、add,都是不會阻塞,直接返回的。而這些方法都是調用的同一個方法:private E xfer(E e, boolean haveData, int how, long nanos)
涉及取出的方法:take、poll、poll(timeout),有數據則出隊,無數據則阻塞。同樣,這些方法都是調用的同一個方法:private E xfer(E e, boolean haveData, int how, long nanos)
由於這個方法被兩種操作共用,僅僅按句註釋,完全不知所以然,所以我拿出一個場景,按照代碼走的過程解釋一遍:take線程先啓動,拿不到數據則阻塞;然後put線程向裏面塞數據;
成員變量
/* * Possible values for "how" argument in xfer method. */ private static final int NOW = 0; // for untimed poll, tryTransfer private static final int ASYNC = 1; // for offer, put, add private static final int SYNC = 2; // for transfer, take private static final int TIMED = 3; // for timed poll, tryTransfer // put、offer、add: xfer(e, true, ASYNC, 0) // take: xfer(null, false, SYNC, 0) // poll: xfer(null, false, NOW, 0) // timed poll: xfer(null, false, TIMED, unit.toNanos(timeout)) // transfer: xfer(e, true, SYNC, 0) // tryTransfer: xfer(e, true, NOW, 0) // timed tryTransfer: xfer(e, true, TIMED, unit.toNanos(timeout)) // ASYNC和NOW不阻塞;SYNC和TIMED會阻塞。
take線程嘗試拿數據
// take線程: xfer(null, false, SYNC, 0) private E xfer(E e, boolean haveData, int how, long nanos) { if (haveData && (e == null)) throw new NullPointerException(); Node s = null; // the node to append, if needed retry: for (;;) { // restart on append race // 從head開始遍歷(第一次插入時隊列爲空,也就是head爲null,直接跳過循環) for (Node h = head, p = h; p != null;) { // find & match first node // 查看當前節點的狀態:有數據則爲true,無數據則爲false boolean isData = p.isData; // 當前節點存儲的數據 Object item = p.item; if (item != p && (item != null) == isData) { // unmatched // 不匹配的情況: put的時候發現head裏有數據(isData = true,haveData = true),take的時候發現head裏是個空節點(isData = false,haveData = false) // 匹配的情況: put的時候發現head裏是個空節點(isData = false,haveData = true),take的時候發現head裏有數據(isData = true,haveData = false) if (isData == haveData) // 不匹配則break break; if (p.casItem(item, e)) { // match for (Node q = p; q != h;) { Node n = q.next; // update by 2 unless singleton if (head == h && casHead(h, n == null ? q : n)) { h.forgetNext(); break; } // advance and retry if ((h = head) == null || (q = h.next) == null || !q.isMatched()) break; // unless slack < 2 } LockSupport.unpark(p.waiter); return LinkedTransferQueue.<E>cast(item); } } Node n = p.next; p = (p != n) ? n : (h = head); // Use head if p offlist } if (how != NOW) { // No matches available if (s == null) s = new Node(e, haveData); // 添加一個空節點new Node(null, false) Node pred = tryAppend(s, haveData); if (pred == null) continue retry; // lost race vs opposite mode if (how != ASYNC) // 阻塞等待,直到節點裏有數據,被put線程喚醒 return awaitMatch(s, pred, e, (how == TIMED), nanos); } return e; // not waiting } }
一句話:take線程拿不到數據,添加一個空節點然後等待
tryAppend
private Node tryAppend(Node s, boolean haveData) { // 從尾部開始 for (Node t = tail, p = t;;) { // move p to last node and append Node n, u; // temps for reads of next & tail // 如果隊列空,直接放在head的位置並返回 if (p == null && (p = head) == null) { if (casHead(null, s)) return s; // initialize } // 如果無法將具有給定模式的節點追加到此節點,則返回true。因爲此節點是不匹配的,並且具有相反的數據模式。 // 比如一個場景,take線程在執行上面casHead之前,put線程已經向隊列裏添加了數據,此時take線程再執行casHead是不會成功的,然後就會走cannotPrecede方法 else if (p.cannotPrecede(haveData)) return null; // lost race vs opposite mode // 不是最後一個節點,則繼續遍歷 else if ((n = p.next) != null) // not last; keep traversing p = p != t && t != (u = tail) ? (t = u) : // stale tail (p != n) ? n : null; // restart if off list // CAS插入到最後失敗,向後遍歷 else if (!p.casNext(null, s)) p = p.next; // re-read on CAS failure else { // 根據鬆弛閾值更新tail if (p != t) { // update if slack now >= 2 while ((tail != t || !casTail(t, s)) && (t = tail) != null && (s = t.next) != null && // advance and retry (s = s.next) != null && s != t); } return p; } } }
awaiMatch
/** * Spins/yields/blocks until node s is matched or caller gives up. * * @param s 等待的節點 * @param pred s的前驅,或者s本身(如果沒有前驅),或者null(如果未知) * @param e 檢查匹配的比較值 * @param timed 如果爲true,則只等待超時時間 * @param nanos 超時時間,只有在timed等於true時使用 * @return 返回匹配項,如果在中斷或超時時未匹配,則返回e */ private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) { // 如果指定了超時,則計算超時時間 final long deadline = timed ? System.nanoTime() + nanos : 0L; // 獲取當前線程 Thread w = Thread.currentThread(); int spins = -1; // initialized after first item and cancel checks ThreadLocalRandom randomYields = null; // bound if needed for (;;) { // 等待節點的當前數據 Object item = s.item; // 如果是take和timed poll方法,e爲null。也就是說,當前數據不爲null,證明此時等待線程可以返回了。 // 如果是transfer和timed tryTransfer方法,e爲待添加的值。也就是說,當前數據爲null被取走了,證明此時等待線程可以返回了。 if (item != e) { // matched // assert item != s; // 清除writer,不再等待 s.forgetContents(); // avoid garbage return LinkedTransferQueue.<E>cast(item); } // 線程中斷或者超時 並且 成功的將s的item設置爲s if ((w.isInterrupted() || (timed && nanos <= 0)) && s.casItem(e, s)) { // cancel // 斷開當前節點和pred的連接 unsplice(pred, s); return e; } // 自旋() if (spins < 0) { // establish spins at/near front // 計算自旋次數 if ((spins = spinsFor(pred, s.isData)) > 0) randomYields = ThreadLocalRandom.current(); } else if (spins > 0) { // spin --spins; if (randomYields.nextInt(CHAINED_SPINS) == 0) // yield()方法會通知線程調度器放棄對處理器的佔用,但調度器可以忽視這個通知。yield()方法主要是爲了保障線程間調度的連續性,防止某個線程一直長時間佔用cpu資源。 // 也就是讓掉當前線程 CPU 的時間片,使正在運行中的線程重新變成就緒狀態,並重新競爭 CPU 的調度權。它可能會獲取到,也有可能被其他線程獲取到。 Thread.yield(); // occasionally yield } // 如果當前節點的waiter爲空,則設置爲當前線程 else if (s.waiter == null) { s.waiter = w; // request unpark then recheck } // 如果設置了超時 else if (timed) { nanos = deadline - System.nanoTime(); if (nanos > 0L) // 在指定的等待時間內,禁止當前線程用於線程調度 LockSupport.parkNanos(this, nanos); } else { // 禁止當前線程用於線程調度 LockSupport.park(this); } } }
=====總體來說,xfer的操作分爲三個階段:第一個在方法xfer中實現,第二個在tryAppend中實現,第三個在方法awaitMatch中實現。=====
1. 嘗試匹配現有節點
從head開始,跳過已經匹配的節點,直到找到一個相反模式的不匹配節點(如果存在的話),在這種情況下匹配它並返回,如果有必要,也會將head更新到經過匹配節點的節點(如果列表中沒有其他不匹配的節點,則是節點本身)。
如果CAS失敗,那麼循環將重試前進頭兩步,直到成功或在鬆弛閾值限制之下最多兩步。2. 嘗試添加一個新節點(方法tryAppend)
從當前尾部指針開始,找到實際的最後一個節點並嘗試添加一個新節點(如果head爲空,則建立第一個節點)。只有在其前一個節點已經匹配或具有相同模式的情況下,纔可以添加節點。
如果檢測到其他情況,則在遍歷過程中必須附加一個模式相反的新節點,因此必須在階段1重新啓動。3.等待匹配或取消(方法awaitMatch)
等待另一個線程匹配節點;相反,如果當前線程被中斷或等待超時,則取消。在多處理器上,我們使用隊列前旋轉:如果一個節點看起來是隊列中第一個不匹配的節點,它會在阻塞之前旋轉一點。
在這兩種情況下,在阻塞之前,它會嘗試將當前“head”和第一個不匹配的節點之間的任何節點進行反拼接。
隊列前端旋轉極大地提高了競爭激烈的隊列的性能。只要它相對簡短且“安靜”,旋轉就不會對競爭較少的隊列的性能造成太大影響。
在spin期間,線程檢查它們的中斷狀態並生成一個線程本地隨機數來決定是否偶爾執行一個Thread.yield。
put線程添加數據
// put: xfer(e, true, ASYNC, 0) private E xfer(E e, boolean haveData, int how, long nanos) { if (haveData && (e == null)) throw new NullPointerException(); Node s = null; // the node to append, if needed retry: for (;;) { // restart on append race // 從head開始遍歷 for (Node h = head, p = h; p != null;) { // find & match first node // 此時隊列裏只有一個空節點,isData = false boolean isData = p.isData; // item = null Object item = p.item; if (item != p && (item != null) == isData) { // unmatched // 由於isData=false,haveData=true,所以不走break if (isData == haveData) // can't match break; // 直接把新數據e添加到當前空節點裏(CAS:如果預期值null和內存值null相同,則將內存值替換成e) if (p.casItem(item, e)) { // match // 基於閾值的方法進行更新,鬆弛閾值爲2。也就是說,噹噹前指針距離第一個或者最後一個節點有兩個或更多步時,更新head/tail。 for (Node q = p; q != h;) { Node n = q.next; // update by 2 unless singleton if (head == h && casHead(h, n == null ? q : n)) { h.forgetNext(); break; } // advance and retry if ((h = head) == null || (q = h.next) == null || !q.isMatched()) break; // unless slack < 2 } // 喚醒阻塞的take線程 LockSupport.unpark(p.waiter); // 返回 return LinkedTransferQueue.<E>cast(item); } } Node n = p.next; p = (p != n) ? n : (h = head); // Use head if p offlist } if (how != NOW) { // No matches available if (s == null) s = new Node(e, haveData); Node pred = tryAppend(s, haveData); if (pred == null) continue retry; // lost race vs opposite mode if (how != ASYNC) return awaitMatch(s, pred, e, (how == TIMED), nanos); } return e; // not waiting } }
這裏是Debug的put過程,發現一個含有等待線程的空節點。
正常包含數據的節點不包含witer
工作過程
首先put操作都是無阻塞直接返回;當take線程來取數據的時候,發現沒有數據,則先new一個空節點(isData屬性爲false,代表這個節點期望能夠添加數據),如果隊列爲空,直接放在頭結點的位置,隊列不爲空則放在尾節點後面。有多個take線程,則依次添加到尾節點。添加完畢則阻塞等待在節點上;當put線程添加數據的時候,如果發現存在take線程等待的空節點,則優先把數據放在空節點,並喚醒阻塞線程取走數據。
再解釋一下“模式”
前面說到第一個階段是從head開始,跳過已經匹配的節點,直到找到一個相反模式的不匹配節點。具體來講就是:
put:haveData=true,與之相匹配節點的模式是isData=true,不匹配則屬isData=false;
take:haveData=false,與之相匹配節點的模式是isData=false,不匹配則屬isData=true;
優缺點
優點:集LinkedBlockingQueue,SynchronousQueue的優點於一身,同時利用CAS實現了無鎖處理,性能得到很大的提高
缺點:無界隊列,使用不當會消耗內存。
看過源碼可以知道這個隊列是沒有鎖的,那麼怎樣保證的線程安全?
可以看到,在覈心方法裏用了很多CAS方法,比如:casHead、casNext等等,其內部調用的都是UNSAFE的本地方法。實際上就是靠這些原子操作的CAS方法來保證的線程安全:原子操作,也就是同一時刻只有一個線程能執行,比如一個put和一個take線程同時執行到casHead(null, s)方法,由於原子性同一時刻只有一個能執行成功,等CPU調度到另一個線程的時候,這個方法是不能成功執行的,然後後面還有後續的判斷來保證不出錯。
你可能會問了,現在的CPU都是多核的,意味着在同一時刻多個線程可以並行處理,那麼這個隊列就不是安全的了。
首先操作系統有內核線程和用戶線程之分:如果是用戶線程,OS是無法感知的,真正的多核CPU處理的是內核級線程。用戶線程的弊端之一就是不能利用多CPU資源。所以,應用裏的線程還是CPU調度來模擬“並行”的假象。
PriorityBlockingQueue
功能
全名
public class PriorityBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable
簡述
使用與PriorityQueue類相同的排序規則並提供阻塞檢索操作的無界阻塞隊列。雖然該隊列在邏輯上是無界的,但是由於資源耗盡(OutOfMemoryError)可能導致嘗試添加的操作失敗。此類不允許空元素。依賴於自然排序的優先級隊列也不允許插入不可比較的對象(這樣做會導致ClassCastException)。
方法
// 將指定的元素插入此優先隊列。 public boolean add(E e) // 將指定的元素插入此優先隊列。因爲隊列是無界的,所以這個方法永遠不會返回false。 public boolean offer(E e) // 將指定的元素插入此優先隊列。因爲隊列是無界的,所以這個方法永遠不會阻塞。 public void put(E e) // 將指定的元素插入此優先隊列。因爲隊列是無界的,所以這個方法永遠不會阻塞或返回false。 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。 public E peek() // 返回用於對該隊列中的元素排序的比較器,如果該隊列使用其元素的自然排序,則返回null。 public Comparator<? super E> comparator() // 返回此集合中的元素數。如果隊列元素大於Integer.MAX_VALUE,則只返回Integer.MAX_VALUE public int size() // 因爲是無界隊列,總返回Integer.MAX_VALUE 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() // 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。 public int drainTo(Collection<? super E> c) // 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上 public int drainTo(Collection<? super E> c, int maxElements) // 刪除此隊列中的所有元素。此調用返回後,隊列將爲空。 public void clear() // 返回此隊列中元素的迭代器。迭代器不會以任何特定的順序返回元素。返回的迭代器是弱一致的。 public Iterator<E> iterator() // 返回該隊列中元素的Spliterator。返回的spliterator是弱一致的。 public Spliterator<E> spliterator()
原理
內部結構
優先隊列的存儲結構是一個數組,邏輯結構是一個平衡的二叉堆(小頂堆),你也可以傳入自定義的Comparator來使之成爲一個大頂堆。
成員變量
/** * 初始容量 */ private static final int DEFAULT_INITIAL_CAPACITY = 11; /** * 內部最大容量 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * 隊列存儲結構 */ private transient Object[] queue; /** * 隊列元素數量 */ private transient int size; /** * 比較器,如果爲空,隊列使用自然順序排序 */ private transient Comparator<? super E> comparator; /** * 重入鎖 */ private final ReentrantLock lock; /** * 當隊列爲空則阻塞 */ private final Condition notEmpty;
add、put、offer(E, long, TimeUnit)都是調用的offer方法
public boolean offer(E e) { if (e == null) throw new NullPointerException(); final ReentrantLock lock = this.lock; lock.lock(); int n, cap; Object[] array; // 只要元素數量大於等於當前容量,則執行擴容 while ((n = size) >= (cap = (array = queue).length)) tryGrow(array, cap); try { // 用戶自定義的比較器 Comparator<? super E> cmp = comparator; if (cmp == null) // 使用自然順序排序。在n的位置上插入e,執行siftUp操作,使得堆結構不變 siftUpComparable(n, e, array); else // 使用傳入的比較器 siftUpUsingComparator(n, e, array, cmp); // 元素數量 +1 size = n + 1; // 通知阻塞的take線程 notEmpty.signal(); } finally { lock.unlock(); } return true; } // 計算出新容量,new一個新容量大小的數組,利用System.arraycopy方法把老數組內容複製到新數組。 private void tryGrow(Object[] array, int oldCap) { lock.unlock(); // must release and then re-acquire main lock Object[] newArray = null; if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) { try { // 如果老容量小於64,則新容量=2*老容量+2;否則新容量=1.5*老容量 int newCap = oldCap + ((oldCap < 64) ? (oldCap + 2) : // grow faster if small (oldCap >> 1)); // 判斷是否超過了最大容量 if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow int minCap = oldCap + 1; if (minCap < 0 || minCap > MAX_ARRAY_SIZE) throw new OutOfMemoryError(); newCap = MAX_ARRAY_SIZE; } // 創建一個新數組 if (newCap > oldCap && queue == array) newArray = new Object[newCap]; } finally { allocationSpinLock = 0; } } if (newArray == null) // back off if another thread is allocating Thread.yield(); lock.lock(); if (newArray != null && queue == array) { queue = newArray; // 把老數組的內容複製到新數組 System.arraycopy(array, 0, newArray, 0, oldCap); } } // 在k位置插入元素x,通過向上提升x直到它大於或等於其父節點或者是根節點,來保持堆不變。 private static <T> void siftUpComparable(int k, T x, Object[] array) { Comparable<? super T> key = (Comparable<? super T>) x; while (k > 0) { // parent=(child-1)/2 int parent = (k - 1) >>> 1; // parent的值 Object e = array[parent]; // 如果新元素大於等於父節點,break if (key.compareTo((T) e) >= 0) break; array[k] = e; // 小於父節點,則繼續向上移動 k = parent; } // 最後把新元素插入到適當的位置 array[k] = key; }
sfitUp過程如下。說白了添加就是把新元素放在最後,然後爲了保持堆的性質,把新元素提升到合適的位置,保證每一棵子樹的根都比左右孩子小。
poll、take和timed poll
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { // 直接返回 return dequeue(); } finally { lock.unlock(); } } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); E result; try { // 隊列爲空,阻塞 while ( (result = dequeue()) == null) notEmpty.await(); } finally { lock.unlock(); } return result; } public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); E result; try { // 隊列爲空,阻塞一定時間 while ( (result = dequeue()) == null && nanos > 0) nanos = notEmpty.awaitNanos(nanos); } finally { lock.unlock(); } return result; }
實際上出隊調用的都是dequeue方法
private E dequeue() { // 最後一個元素的下標 int n = size - 1; // 隊列空,返回null if (n < 0) return null; else { // 當前內部數組 Object[] array = queue; // 保存隊頭元素 E result = (E) array[0]; // 臨時變量保存數組最後一個元素,也就是隊尾元素 E x = (E) array[n]; // 最後一個元素置空 array[n] = null; Comparator<? super E> cmp = comparator; // 根據有無比較器執行對應的siftDown操作 if (cmp == null) // 這裏可以看出,當出隊的時候,先刪除第一個元素,然後把最後一個元素添加到第一位。然後再進行整理以保證堆的性質不變。因爲最後一個元素肯定比第一個大,所以要把它向下降級。 siftDownComparable(0, x, array, n); else siftDownUsingComparator(0, x, array, n, cmp); // 修改size size = n; return result; } } // 將元素x插入到k的位置,通過循環將x降級到樹的下面,直到它小於或等於它的子元素,或者是葉子,從而保持堆不變。 // dequeue操作:k=0,x=最後一個元素,n=最後一個元素下標 private static <T> void siftDownComparable(int k, T x, Object[] array, int n) { if (n > 0) { Comparable<? super T> key = (Comparable<? super T>)x; // 無符號右移1位,也就是n/2 int half = n >>> 1; // loop while a non-leaf while (k < half) { // child=2*parent+1(child存儲的是左右孩子中較小的那一個) int child = (k << 1) + 1; // 假設左孩子最小 // 當前節點的左孩子的值 Object c = array[child]; // 右孩子=左孩子+1 int right = child + 1; // 右孩子的下標小於最後一個元素的下標,並且左孩子大於右孩子 if (right < n && ((Comparable<? super T>) c).compareTo((T) array[right]) > 0) // 此時,較小的孩子爲右孩子,將右孩子的值和下標存起來 c = array[child = right]; // 如果此時元素x小於等於它孩子中較小的一個,break if (key.compareTo((T) c) <= 0) break; array[k] = c; // 大於孩子節點,繼續向下 k = child; } // 最後把元素插到合適的位置 array[k] = key; } }
siftDown過程如下。說白了,出隊就是把第一個元素刪除,然後拿出最後一個元素x,從堆的第一個元素的位置k=0開始,x與左右孩子中較小的一個c相比較,如果x大,k指向c的位置,當前位置存儲c,進入下一次比較;最後把x放在k指向的位置。
這裏爲什麼不說“交換”了呢,因爲這纔是嚴謹的說法,在實際上也並沒有比較一次交換一次,而是把僅僅把當前k的位置填充了c,c的原位置仍然存儲的c,直到下一次比較出了結果。
在前面的入隊過程,直接用了“交換”,因爲這樣比較直白,容易理解,但是要記住實際實現並不是“交換”。
對於remove方法,是先找到目標的下標,然後根據情況做siftUp或者siftDown處理
public boolean remove(Object o) { final ReentrantLock lock = this.lock; lock.lock(); try { // 先找到下標 int i = indexOf(o); if (i == -1) return false; // 移除指定位置的元素 removeAt(i); return true; } finally { lock.unlock(); } } private void removeAt(int i) { Object[] array = queue; int n = size - 1; // 特殊情況,移除最後一個元素,很好處理 if (n == i) // removed last element array[i] = null; else { // 最後一個元素 E moved = (E) array[n]; array[n] = null; Comparator<? super E> cmp = comparator; if (cmp == null) // 用最後一個元素moved填充到刪除的i位置,然後爲了保障堆性質,對moved做降級處理 siftDownComparable(i, moved, array, n); else siftDownUsingComparator(i, moved, array, n, cmp); // 如果在i位置上的moved元素不需要做降級處理,則需要做提升處理 if (array[i] == moved) { if (cmp == null) siftUpComparable(i, moved, array); else siftUpUsingComparator(i, moved, array, cmp); } } size = n; }
優缺點
這個是PriorityQueue的阻塞隊列版本,實現大致一樣。優先級隊列可以對任務進行排序,不過因爲這個是個無界隊列,生產者控制不好會有內存問題。
SynchronousQueue
功能
全名
public class SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable
簡述
一個阻塞隊列,其中每個插入操作必須等待另一個線程的相應刪除操作,反之亦然。同步隊列沒有任何內部容量。
您無法查看同步隊列,因爲一個元素只有在您試圖刪除它時纔會出現;你不能插入一個元素(使用任何方法),除非另一個線程試圖刪除它;
您不能進行迭代,因爲沒有需要迭代的內容。隊列的頭是第一個隊列插入線程試圖添加到隊列中的元素;如果沒有這樣的排隊線程,那麼就沒有可以刪除的元素,並且poll()將返回null。
它們非常適合於切換設計,在這種設計中,在一個線程中運行的對象必須與在另一個線程中運行的對象同步,以便傳遞一些信息、事件或任務。
該類支持一個可選的公平性策略,用於對正在等待的生產者和消費者線程進行排序。默認情況下,不保證這種順序。但是,將公平性設置爲true的隊列將按FIFO順序授予線程訪問權。SynchronousQueue的內部實現了兩個類,一個是TransferStack類,使用LIFO順序存儲元素,這個類用於非公平模式;還有一個類是TransferQueue,使用FIFI順序存儲元素,這個類用於公平模式。
方法
// 將指定的元素添加到此隊列中,如有必要則等待另一個線程接收它。 public void put(E e) throws InterruptedException // 將指定的元素插入此隊列,如有必要,將等待到指定的等待時間,以便另一個線程接收它。 public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException // 如果另一個線程正在等待接收指定的元素,則將其插入此隊列。 public boolean offer(E e) // 檢索並刪除此隊列的頭,如有必要等待另一個線程插入它。 public E take() throws InterruptedException // 檢索並刪除此隊列的頭,如有必要,則在超時時間之內等待另一個線程插入它。 public E poll(long timeout, TimeUnit unit) throws InterruptedException // 如果另一個線程當前正在使某個元素可用,則檢索並刪除此隊列的頭。 public E poll() // 總是返回true。同步隊列沒有內部容量。 public boolean isEmpty() // 總是返回0。同步隊列沒有內部容量。 public int size() // 總是返回0。同步隊列沒有內部容量。 public int remainingCapacity() // 什麼也不做。同步隊列沒有內部容量。 public void clear() // 總是返回false。同步隊列沒有內部容量。 public boolean contains(Object o) // 總是返回false。同步隊列沒有內部容量。 public boolean remove(Object o) // 返回false,除非指定的集合爲空。同步隊列沒有內部容量。 public boolean containsAll(Collection<?> c) // 總是返回false。同步隊列沒有內部容量。 public boolean removeAll(Collection<?> c) // 總是返回false。同步隊列沒有內部容量。 public boolean retainAll(Collection<?> c) // 總是返回null。除非主動等待,否則同步隊列不會返回元素。 public E peek() // 返回一個空迭代器,其中hasNext總是返回false。 public Iterator<E> iterator() // 返回一個空的spliterator,其中對Spliterator.trysplit()的調用總是返回null。 public Spliterator<E> spliterator() // 返回一個0長度數組。 public Object[] toArray() // 將指定數組的zeroeth元素設置爲null(如果數組長度非零)並返回它。 public <T> T[] toArray(T[] a) // 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。 public int drainTo(Collection<? super E> c) // 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上 public int drainTo(Collection<? super E> c, int maxElements)
原理
首先transfer在SynchronousQueue就已經有體現了,只不過沒有暴露給開發者。
abstract static class Transferer<E> { /** * Performs a put or take. * * @param e if non-null, the item to be handed to a consumer; * if null, requests that transfer return an item * offered by producer. * @param timed if this operation should timeout * @param nanos the timeout, in nanoseconds * @return if non-null, the item provided or received; if null, * the operation failed due to timeout or interrupt -- * the caller can distinguish which of these occurred * by checking Thread.interrupted. */ abstract E transfer(E e, boolean timed, long nanos); }
這個抽象類有兩種實現,一個用於公平模式,一個用於非公平模式。
// 默認是非公平的 public SynchronousQueue() { this(false); } // 也可以手動指定 public SynchronousQueue(boolean fair) { transferer = fair ? new TransferQueue<E>() : new TransferStack<E>(); }
先簡單說一句,對於put、timed offer方法,如果沒有對應的消費者,則會阻塞,而offer方法如果沒有消費者則直接返回;對於take、timed poll方法,如果沒有對應的生產者,則會阻塞,而poll方法如果沒有對應生產者則直接返回。
而他們調用的都是同一個方法:transfer,所以下面只分析這個方法。
關於公平與非公平:
公平模式:CAS + 利用FIFO隊列先進先出的性質來阻塞多餘的生產者和消費者,保證隊頭是等待時間最長的元素;
非公平模式:CAS + 利用LIFO棧先進後出的性質來阻塞多餘的生產者和消費者,不保證生產消費順序,容易出現飢渴的情況;
TransferStack
先來看一下成員變量(內部結構是一個鏈表,存儲每一個阻塞的線程。當put阻塞的時候,mode=1;當take阻塞的時候,mode=0)
put
take
未完待續
TransferQueue
優缺點