源碼分析BlockingQueue實現(jdk1.8)

BlockingQueue

BlockingQueue常常運用於線程池的阻塞隊列中,顧名思義,它能夠作爲一個具有阻塞作用先進先出隊列。
BlockingQueue 對插入操作、移除操作、獲取元素操作提供了四種不同的方法用於不同的場景中使用:1、拋出異常;2、返回特殊值(null 或 true/false,取決於具體的操作);3、阻塞等待此操作,直到這個操作成功;4、阻塞等待此操作,直到成功或者超時指定時間。總結如下:

throw exception special value blocks times out
insert add(e) offer(e) put(e) offer(e,time,unit)
delete remove() poll() take() poll(time,unit)
examine element() peek() not applicable not applicable

實現1:ArrayBlockingQueue

ArrayBlockingQueue:基於數組結構的有界阻塞隊列,併發控制通過ReentrantLock獲得鎖來實現。
併發同步原理:讀操作和寫操作都需要獲取到 AQS 獨佔鎖才能進行操作【對比之下LinkedBlockingQueue有兩個鎖,分別爲讀取鎖和寫入鎖,能同時讀和寫】。如果隊列爲空,這個時候讀操作的線程進入到讀線程隊列排隊,等待寫線程寫入新的元素,然後喚醒讀線程隊列的第一個等待線程。如果隊列已滿,這個時候寫操作的線程進入到寫線程隊列排隊,等待讀線程將隊列元素移除騰出空間,然後喚醒寫線程隊列的第一個等待線程。

屬性及構造函數

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
     /**
     * 存放元素的數組
     */
    final Object[] items;

    /**
     * 下一次讀取操作的位置
     */
    int takeIndex;

    /**
     * 下一次存放操作的位置
     */
    int putIndex;

    /**
     * 隊列中對象數量
     */
    int count;
   
   //下面三個爲控制併發用到的同步器
    final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;

   //設定隊列容量的構造函數,默認非公平鎖
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
   //設定隊列容量和鎖公平性的構造函數
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }

//指定集合的構造函數,將此集合中的元素在構造方法期間就先添加到隊列中。
 public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
        this(capacity, fair);
        final ReentrantLock lock = this.lock;
        lock.lock(); // Lock only for visibility, not mutual exclusion
        try {
            int i = 0;
            try {
                for (E e : c) {
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            count = i;
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }
}

put

    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();
        }
    }

    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

take

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return 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;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

實現2:LinkedBlockingQueue

LinkedBlockingQueue:基於鏈表結構的“無界”阻塞隊列,併發控制同樣通過ReentrantLock獲得鎖來實現,不過LinkedBlockingQueue採用了雙鎖:讀取鎖和寫入鎖,相比ArrayBlockingQueue能夠同時進行讀和寫,吞吐量更高! 線程池Executors.newFixedThreadPool()採用了此隊列。
併發同步原理

使用了兩個鎖,兩個Condition
takeLocknotEmpty 搭配:如果獲取(take)一個元素,首先要獲得鎖(takeLock),這還不夠,還要繼續判斷隊列是否爲空(notEmpty)這個條件(Condition)
putLocknotFull 搭配:如果插入(put)一個元素,首先要獲得鎖(putLock),這還不夠,還要繼續判斷隊列是否已滿(notFull)這個條件(Condition)

屬性及構造函數

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    
    //很簡單的結點結構,包含了該結點的元素和後繼結點    
    static class Node<E> {
        E item;
        Node<E> next;
		Node(E x) { item = x; }
    }

 	//隊列容量
    private final int capacity;

   //隊列中元素數量
    private final AtomicInteger count = new AtomicInteger();

    //隊頭結點
    transient Node<E> head;

    //隊頭=尾結點
    private transient Node<E> last;

    //元素讀取操作時需要獲得的鎖
    private final ReentrantLock takeLock = new ReentrantLock();

    private final Condition notEmpty = takeLock.newCondition();

    //元素存入操作時需要獲得的鎖
    private final ReentrantLock putLock = new ReentrantLock();

    private final Condition notFull = putLock.newCondition();

    //因爲默認隊列長度大小爲有符號整數的最大值,所以我們稱其爲“無界隊列”
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    //可設定初始隊列容量的構造函數,這樣它就有界了
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        //初始化空的頭結點
        last = head = new Node<E>(null);
    }
    //利用集合初始化的構造函數
    public LinkedBlockingQueue(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // Never contended, but necessary for visibility
        try {
            int n = 0;
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                if (n == capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }

put

      public void put(E e) throws InterruptedException {
        //元素不能爲null
        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.
        //c初始化爲-1 ,用於標識插入操作是否成功,offer方法返回布爾值就是通過c是否>=0來判斷的
        int c = -1;
        Node<E> node = new Node<E>(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();
            //c + 1 < capacity說明隊列未滿,調用 notFull.signal() 喚醒等待線程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            //入隊後釋放鎖
            putLock.unlock();
        }
        //c==0說明之前隊列是空的,這時插入一個元素後隊列不爲空,因此喚醒notEmpty這個條件
        if (c == 0)
            signalNotEmpty();
    }

	//入隊很簡單,將新結點插入到隊列末尾,last指針更新
    private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        last = last.next = node;
    }

take

    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) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

	//出隊
    private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
        //頭結點一直保持爲null,每次從隊列中取元素實際上是取頭結點後面一個節點的值
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }

實現3:SynchronousQueue

SynchronousQueue:不存儲元素的阻塞隊列,即隊列是虛的,不存儲元素,吞吐量通常高於LinkedBlockingQueue ,線程池Executors.newCacheThreadPool()採用了此隊列。

併發同步原理

當一個線程往隊列中寫元素時,寫入操作不會立即返回,需要等待另一個線程來將這個元素拿走;同理當一個讀線程做讀操作的時候,同樣需要一個相匹配的寫線程的寫操作。
這裏的Synchronous指的就是寫線程要和讀線程同步,一個寫線程匹配一個讀線程。
SynchronousQueue 的隊列其實是虛的,其不提供任何空間(一個都沒有)來存儲元素。數據必須從某個寫線程交給某個讀線程,而不是寫到某個隊列中等待被消費。

屬性及構造函數

SynchronousQueue內部有一個抽象類Transferer,它的兩個實現類TransferQueue和TransferStack分別用於公平和非公平模式下。
在這裏插入圖片描述
在這裏插入圖片描述

public class SynchronousQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    
    //默認非公平模式    
	public SynchronousQueue() {
        this(false);
    }
    //設定公平模式的構造函數,公平用TransferQueue,非公平用TransferStack
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }



	//抽象類Transferer中只有一個抽象方法,讓我們來看一下:
    abstract static class Transferer<E> {
        /**
         * Performs a put or take.
         *
         * @param e     如果非null,表示將元素從生產者轉移到消費者
         *              如果爲null,表示消費者等待生產者提供元素,返回值爲生產者提供的元素
         * @param timed 是否設置超時
         * @param 超時時間
         * @return 非空,則表示轉移的元素;
         * 空,則表示超時或者中斷。
         * 調用者可以通過線程的中斷狀態判斷具體是哪種情況導致的
         */
        abstract E transfer(E e, boolean timed, long nanos);
    }

}

我們先採用公平模式分析源碼,然後再說說公平模式和非公平模式的區別。

put和take

     public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        //根據transfer的返回值是否爲null來判斷是否轉移元素成功
        if (transferer.transfer(e, false, 0) == null) {
            //未成功,線程中斷,拋出異常
            Thread.interrupted();
            throw new InterruptedException();
        }
    }


    public E take() throws InterruptedException {
        //和put的區別在於第一個參數爲null,表示取元素
        E e = transferer.transfer(null, false, 0);
        //根據transfer的返回值是否爲null來判斷是否轉移元素成功
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }

由代碼可以看出take和put操作都是通過transfer實現的,它們對此方法實現最大的區別在於方法的第一個參數,put不爲null,take爲null,可以說SynchronousQueue的核心功能就是通過transfer的實現的,下面來看transfer方法。


transfer

公平模式

transfer的設計思路:

  1. 調用此方法時,若隊列爲空或者隊列中的結點和當前線程的操作類型一致(即當前操作爲put操作,隊列中的結點的線程屬性都爲寫線程;take操作和讀線程同理),則將當前線程加入到等待隊列中
    2.如果隊列中有等待結點,而且與當前操作匹配(即當前操作位put操作,隊列中結點線程線程屬性都爲讀線程,這就構成了匹配;take操作和寫線程同理)。這種情況下,匹配等待隊列的隊頭,出隊,返回響應數據。

下面來看看官方註釋:

           /* Basic algorithm is to loop trying to take either of
             * two actions:
             *
             * 1. If queue apparently empty or holding same-mode nodes,
             *    try to add node to queue of waiters, wait to be
             *    fulfilled (or cancelled) and return matching item.
             *
             * 2. If queue apparently contains waiting items, and this
             *    call is of complementary mode, try to fulfill by CAS'ing
             *    item field of waiting node and dequeuing it, and then
             *    returning matching item.
             *
             * In each case, along the way, check for and try to help
             * advance head and tail on behalf of other stalled/slow
             * threads.
             *
             * The loop starts off with a null check guarding against
             * seeing uninitialized head or tail values. This never
             * happens in current SynchronousQueue, but could if
             * callers held non-volatile/final ref to the
             * transferer. The check is here anyway because it places
             * null checks at top of loop, which is usually faster
             * than having them implicitly interspersed.
             */

對於上面提到的隊列結點,它的結構是這樣的:

       /**
         * 等待隊列的結點類
         */
        static final class QNode {
            volatile QNode next;          // 只有有個後繼結點指針,說明是單向鏈表
            volatile Object item;         // CAS'ed to or from null
            volatile Thread waiter;       // 保存線程對象,用於掛起和喚醒
            final boolean isData;          //判斷是寫線程結點還是讀線程結點  boolean isData = (e != null);
       }

下面正式分析transfer源碼:

//==================TransferQueue的transfer方法===============================
       @SuppressWarnings("unchecked")
        E transfer(E e, boolean timed, long nanos) {
            QNode s = null; // constructed/reused as needed
            boolean isData = (e != null);
            for (; ; ) {
                QNode t = tail;
                QNode h = head;
                //頭結點和尾結點爲空,說明還沒有初始化,continue即可。
                if (t == null || h == null)         // saw uninitialized value
                    continue;                       // spin

                //隊列已初始化,只有一個空結點或者當前結點與隊列結點類型一致
                if (h == t || t.isData == isData) { // empty or same-mode
                    QNode tn = t.next;
                    //說明剛纔有結點入隊,繼續continue即可
                    if (t != tail)                  // inconsistent read
                        continue;
                    if (tn != null) {               // lagging tail
                        //尾結點的後繼結點不爲空,說明不是真正的尾結點,CAS將後繼結點設爲尾結點,繼續循環判斷
                        advanceTail(t, tn);
                        continue;
                    }
                    //用於超時設置
                    if (timed && nanos <= 0)        // can't wait
                        return null;
                    if (s == null)
                        s = new QNode(e, isData);
                    if (!t.casNext(null, s))        // failed to link in
                        continue;

                    //將新結點s設爲新的尾結點
                    advanceTail(t, s);              // swing tail and wait
                    Object x = awaitFulfill(s, e, timed, nanos);

                    //當這裏,說明之前的線程被喚醒了

                    //x==s,說明線程等待超時或者被中斷,就要取消等待
                    if (x == s) {                   // wait was cancelled
                        clean(t, s);
                        return null;
                    }

                    if (!s.isOffList()) {           // not already unlinked
                        advanceHead(t, s);          // unlink if head
                        if (x != null)              // and forget fields
                            s.item = s;
                        s.waiter = null;
                    }
                    return (x != null) ? (E) x : e;

                }
                // 這裏的 else 分支就是上面說的第二種情況,有相應的讀或寫相匹配的情況
                else {                            // complementary-mode
                    QNode m = h.next;               // node to fulfill
                    if (t != tail || m == null || h != head)
                        continue;                   // inconsistent read

                    Object x = m.item;
                    if (isData == (x != null) ||    // m already fulfilled
                            x == m ||                   // m cancelled
                            !m.casItem(x, e)) {         // lost CAS
                        advanceHead(h, m);          // dequeue and retry
                        continue;
                    }

                    advanceHead(h, m);              // successfully fulfilled
                    LockSupport.unpark(m.waiter);
                    return (x != null) ? (E) x : e;
                }
            }

        Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
            /* Same idea as TransferStack.awaitFulfill */
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Thread w = Thread.currentThread();
            //判斷需要自旋的次數
            int spins = ((head.next == s) ?
                    (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            //循環
            for (; ; ) {
                //如果被中斷了,則取消該結點,就是將s中的item屬性設爲this
                if (w.isInterrupted())
                    s.tryCancel(e);
                Object x = s.item;
                //方法的唯一出口,調用tryCancel後(超時或者中斷),x!=e
                if (x != e)
                    return x;
                //如果需要,檢查是否超時
                if (timed) {
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        s.tryCancel(e);
                        continue;
                    }
                }
                if (spins > 0)
                    --spins;
                    //自旋次數已到,先檢查s的線程是否爲空,爲空則設置爲當前線程
                else if (s.waiter == null)
                    s.waiter = w;
                    //自旋次數已到,並且s也封裝了當前的線程,則掛起
                else if (!timed)
                    LockSupport.park(this);
                    //自旋次數已到,當有設置超時時, spinForTimeoutThreshold 這個之前講 AQS 的時候其實也說過,剩餘時間小於這個閾值的時候,就
                    // 不要進行掛起了,自旋的性能會比較好
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }
 	}

非公平模式

通過分析TransferQueue的transfer源碼大概瞭解了公平模式 下的put和get操作,對於非公平模式,也就是TransferStack的transfer源碼實現,直接敘述不加以分析了(參考:https://www.javadoop.com/post/java-concurrent-queue)

  1. 當調用這個方法時,如果隊列是空的,或者隊列中的節點和當前的線程操作類型一致(如當前操作是 put 操作,而棧中的元素也都是寫線程)。這種情況下,將當前線程加入到等待棧中,等待配對。然後返回相應的元素,或者如果被取消了的話,返回 null。
  2. 如果棧中有等待節點,而且與當前操作可以匹配(如棧裏面都是讀操作線程,當前線程是寫操作線程,反之亦然)。將當前節點壓入棧頂,和棧中的節點進行匹配,然後將這兩個節點出棧。配對和出棧的動作其實也不是必須的,因爲下面的一條會執行同樣的事情。
  3. 如果棧頂是進行匹配而入棧的節點,幫助其進行匹配並出棧,然後再繼續操作。
    應該說,TransferStack 的源碼要比 TransferQueue 的複雜一些,如果讀者感興趣,請自行進行源碼閱讀。

兩種模式的對比總結

公平模式

  1. 採用FIFO隊列思想,隊尾匹配隊頭出隊,先進先出,體現了公平原則;
  2. 新來的線程若匹配成功,不需要入隊,直接喚醒隊頭線程(注意:head結點是空節點,這裏是指head結點的後繼結點)

非公平模式:

  1. 採用棧思想,棧頂元素先匹配,先入棧的線程結點後匹配,體現了非公平原則
  2. 新來的線程若匹配成功,則需要壓棧,然後新線程循環執行匹配線程邏輯,一旦發現沒有併發衝突則成對出棧

這篇文章 https://zhuanlan.zhihu.com/p/29227508 用圖來表示兩種模式的put和take過程,清晰易懂

實現4:PriorityBlockingQueue

PriorityBlockingQueue:一個具有優先級的無界阻塞隊列。優先級是因爲採用堆思想,可以根據元素自身排序,也可以指定比較器進行排序;無界是因爲隊列能夠自動擴容。

併發同步原理

當一個線程往隊列中寫元素或者讀取元素時,會獲取獨佔鎖(ReentrantLock),和ArrayBlocking的同步實現原理很像,區別在於PriorityBlockingQueue中沒有notFull這個條件(Condition)

屬性及構造函數

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
    //默認初始隊列容量
    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;
  	//條件Condition
    private final Condition notEmpty;
    //擴容時採用的鎖,通過CAS實現
    private transient volatile int allocationSpinLock;

    //默認構造器,初始化隊列大小爲11
    public PriorityBlockingQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

   //指定隊列大小的構造器
    public PriorityBlockingQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

    //指定隊列大小和比較器的隊列
    public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.comparator = comparator;
        this.queue = new Object[initialCapacity];
    }
}

put

       public void put(E e) {
        offer(e); // never need to block
    }

      public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        //1 獲取獨佔鎖
        lock.lock();
        int n, cap;
        Object[] array;
        //2. 插入元素前判斷是否需要擴容
        while ((n = size) >= (cap = (array = queue).length))
            tryGrow(array, cap);
         //3. 插入元素並調整堆結構
        try {
            Comparator<? super E> cmp = comparator;
            //構造器爲空,使用插入元素自帶比較器進行調整
            if (cmp == null)
                siftUpComparable(n, e, array);
            //否則使用指定的構造器進行調整
            else
                siftUpUsingComparator(n, e, array, cmp);
            size = n + 1;
		//4. 通知notEmpty條件
            notEmpty.signal();
        } finally {
        //5. 釋放鎖
            lock.unlock();
        }
        return true;
    }

由於在插入元素前會判斷是夠需要擴容,下面來講一下如何擴容

tryGrow擴容

   /**
     * 擴容過程中釋放了獨佔鎖,通過CAS 操作維護 allocationSpinLock狀態,實現專門的擴容鎖, 這樣就可以保證擴容操作和讀操作同時進行
     * @param array
     * @param oldCap
     */
    private void tryGrow(Object[] array, int oldCap) {
        //釋放獨佔鎖
        lock.unlock(); // must release and then re-acquire main lock
        Object[] newArray = null;
        // 用 CAS 操作將 allocationSpinLock 由 0 變爲 1,算是獲取擴容鎖
        if (allocationSpinLock == 0 &&
            UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                     0, 1)) {
            try {
                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;
                }
                //queue如果不等於array,說明有其他線程給queue分配了空間,newArray爲null
                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);
        }
    }

take

    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;
    }

//元素出隊列,並調整堆結構
    private E dequeue() {
        int n = size - 1;
        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;
            if (cmp == null)
                siftDownComparable(0, x, array, n);
            else
                siftDownUsingComparator(0, x, array, n, cmp);
            size = n;
            return result;
        }
    }

總體來說,PriorityBlockingQueue 也是比較簡單的,內部用數組實現堆結構,通過一個獨佔鎖來控制put和take的線程安全性,通過CAS操作改變allocationSpinLockOffset狀態來達到獲取擴容鎖的目的,這樣擴容操作和讀操作可以同時進行,提高吞吐量。

總結

ArrayBlockingQueue :基於數組結構的有界阻塞隊列,併發控制通過一個ReentrantLock和兩個Condition實現,使用生產者-消費者模式的很好選擇。
LinkedBlockingQueue :基於鏈表結構的無界阻塞隊列,可以設置初始隊列容量使其有界,併發控制通過兩個個ReentrantLock和兩個Condition實現。
SynchronousQueue :本身不帶有空間來存儲任何元素,分爲公平模式和非公平模式兩種,公平模式通過FIFO隊列思想來實現,非公平模式通過棧思想來實現。
PriorityBlockingQueue :帶有優先級的無界阻塞隊列,基於數組實現堆結構,能夠自動擴容。

參考

https://www.javadoop.com/post/java-concurrent-queue#toc0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章