2.2 棧和隊列—隊列

什麼是隊列

隊列是一種先進先出(FIFO:First In First Out)的線性表,只允許從一端插入,從另一端刪除,允許插入的一端叫隊尾(rear),允許刪除的一端叫隊頭(front)。

隊列的模型

隊列模型

隊列可以用數組或者鏈表進行實現,我這邊使用鏈表進行實現。通過鏈表實現的隊列叫鏈式隊列,通過數組實現的隊列叫順序隊列,需要注意的是還有一種特殊的隊列叫循環隊列,實現循環隊列。

操作

  • 入隊: public void enQueue(E e)

入隊

入隊操作就是將新加入的節點放入隊尾即可,也就是鏈表的 add() 方法。

  • 出隊:public E deQueue()

出隊

出隊操作就是將隊頭節點移除,然後對頭節點向後移動移位,對於鏈表來說,也就是移除鏈表頭節點的過程。

  • 獲取隊列大小: public int size()

直接獲取屬性鏈表的大小即可。

  • 判斷隊列是否爲空:public boolean isEmpty()

直接判斷屬性鏈表是否爲空即可。

完整代碼


package com.qucheng.qingtian.wwjd.datastructure;

/**
 * 隊列
 *
 * @author 阿導
 * @CopyRight 萬物皆導
 * @Created 2019-12-12 11:08:00
 */
public class DaoLinkQueue<E> {
    /**
     * 隊列中鏈表屬性
     */
    private DaoDoubleLinkedList<E> linkedList;
    /**
     * 構造函數
     *
     * @author 阿導
     * @time 2019/12/12 :00
     * @return
     */
    public DaoLinkQueue() {
        this.linkedList = new DaoDoubleLinkedList<>();
    }
    /**
     * 入隊
     *
     * @author 阿導
     * @time 2019/12/12 :00
     * @param e
     * @return void
     */
    public void enQueue(E e) {
        this.linkedList.add(e);
    }
    /**
     * 出隊
     *
     * @author 阿導
     * @time 2019/12/12 :00
     * @return boolean
     */
    public E deQueue() {
        if(this.isEmpty()){
            throw new RuntimeException("隊列中元素已空");
        }
        return this.linkedList.remove(0);
    }
    /**
     * 判斷是否爲空
     *
     * @author 阿導
     * @time 2019/12/12 :00
     * @return boolean
     */
    public boolean isEmpty() {
        return this.linkedList.isEmpty();
    }

    /**
     * 獲取大小
     *
     * @author 阿導
     * @time 2019/12/12 :00
     * @return int
     */
    public int size() {
        return this.linkedList.size();
    }
}


線程池中的隊列

我們學習線程池的時候,線程池沒有空閒線程時,新任務請求線程資源時,線程池裏面時何如處理的?

基於這個問題,一般有兩種處理方式,第一種時非阻塞的處理方式,直接拒絕任務請求,另一種時阻塞的處理方式,將請求排隊,等到有空閒線程的時候再取出排隊的請求進行處理,這裏面就涉及到用隊列存儲請求的問題。接下來看看基於數組和鏈表實現的隊列的區別。

基於鏈表實現的鏈式隊列,可以實現一個支持無限排隊的無界隊列(unbounded queue),但是可能會導致過多的請求排隊等待,請求處理的響應時間過長。所以針對響應時間比較敏感的系統,基於鏈表實現的無限排隊的線程池是不合適的。

基於數組實現的有界隊列(bounded queue),隊列大小有限,所以線程池中排隊的請求超過隊列大小時,接下來的請求就會被拒絕,這種方式隊響應時間敏感的系統來說,就相對更爲合理。所以設置隊列的大小很是關鍵,設置合理才能最大限度的利用系統資源發揮其性能,也不會讓更多的請求阻塞,等待很長時間。

LinkedBlockingQueue

先看看 JDK 中隊列的 UML 圖

jdk 中 隊列的 UML 圖

下面我們來剖析一下 LinkedBlockingQueue 源碼,其它源碼感興趣的可自行研究,另外阿導說的不好的地方請多多包含和指教。

首先裏面有個內部靜態類 Node 存儲節點信息的,沒啥好說的。具體代碼如下:


    /**
     * Linked list node class
     */
    static class Node<E> {
        E item;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }


下面看看 LinkedBlockingQueue 裏面的屬性


public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
        /** The capacity bound, or Integer.MAX_VALUE if none */
        // capacity 設置隊列的邊界,默認爲 Integer.MAX_VALUE
        private final int capacity;
    
        /** Current number of elements */
        // 統計隊列裏面的元素個數
        private final AtomicInteger count = new AtomicInteger();
    
        /**
         * Head of linked list.
         * Invariant: head.item == null
         * 
         * 鏈表的頭節點,裏面元素爲空是不變的
         * 
         */
        transient Node<E> head;
    
        /**
         * Tail of linked list.
         * Invariant: last.next == null
         * 
         * 鏈表的尾節點,裏面下一個節點爲 null 是不變的
         */
        private transient Node<E> last;
    
        /** Lock held by take, poll, etc */
        // 協助 tabke ,poll 等操作的可重入鎖
        private final ReentrantLock takeLock = new ReentrantLock();
    
        /** Wait queue for waiting takes */
        // 執行 takes 操作進行阻塞輔助
        private final Condition notEmpty = takeLock.newCondition();
    
        /** Lock held by put, offer, etc */
        // 協助 put,offer 等操作的可重入鎖
        private final ReentrantLock putLock = new ReentrantLock();
    
        /** Wait queue for waiting puts */
        // 執行 puts 操作進行阻塞輔助
        private final Condition notFull = putLock.newCondition();
   
    }
    

下面纔是重頭戲,阿導將逐一講解其裏面的方法,阿導就從源碼的順序往下說。

  • 構造方法:public LinkedBlockingQueue()、public LinkedBlockingQueue(int capacity)、public LinkedBlockingQueue(Collection<? extends E> c)


    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        // 調用 public LinkedBlockingQueue(int capacity),默認是整型的最大值
        this(Integer.MAX_VALUE);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity} is not greater
     *         than zero
     */
    public LinkedBlockingQueue(int capacity) {
        // 閾值校驗
        if (capacity <= 0) throw new IllegalArgumentException();
        // 賦值給閾值
        this.capacity = capacity;
        // 新增頭尾節點,但裏面元素均爲空,顯然空隊列的時候,頭尾指針指向的是同一個節點
        last = head = new Node<E>(null);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}, initially containing the elements of the
     * given collection,
     * added in traversal order of the collection's iterator.
     *
     * @param c the collection of elements to initially contain
     * @throws NullPointerException if the specified collection or any
     *         of its elements are null
     *         
     *         這個構造方法傳入了一個集合,
     */
    public LinkedBlockingQueue(Collection<? extends E> c) {
        // 第一步調用 public LinkedBlockingQueue(int capacity),閾值是整型的最大值
        this(Integer.MAX_VALUE);
        // 獲取 putLock 鎖對象
        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));
                // 統計值自加 1
                ++n;
            }
            // 設置隊列的大小
            count.set(n);
        } finally {
            // 最後釋放鎖
            putLock.unlock();
        }
    }
    
    
     /**
      * Links node at end of queue.
      * 入隊操作
      * @param node the node
      */
     private void enqueue(Node<E> node) {
         // assert putLock.isHeldByCurrentThread();
         // assert last.next == null;
         // 這個操作可拆分爲 last.next = node; last = node;
         last = last.next = node;
     }    
    
  • 獲取隊列的大小:public int size()

    /**
     * Returns the number of elements in this queue.
     *
     *  count 是 AtomicInteger 類型,保證其原子性,裏面是通過 unsafe 裏面的 CAS 來實現的
     * 
     * @return the number of elements in this queue
     */
    public int size() {
        return count.get();
    }

  • 獲取隊列可用容量:public int remainingCapacity()

    /**
     * Returns the number of additional elements that this queue can ideally
     * (in the absence of memory or resource constraints) accept without
     * blocking. This is always equal to the initial capacity of this queue
     * less the current {@code size} of this queue.
     *
     * <p>Note that you <em>cannot</em> always tell if an attempt to insert
     * an element will succeed by inspecting {@code remainingCapacity}
     * because it may be the case that another thread is about to
     * insert or remove an element.
     * 
     * capacity 爲最大容量,count.get()爲當前已使用數量,它們之差即爲隊列剩餘節點數量
     */
    public int remainingCapacity() {
        return capacity - count.get();
    }
    
    
  • 入隊(阻塞):public void put(E e) throws InterruptedException

   /**
     * Inserts the specified element at the tail of this queue, waiting if
     * necessary for space to become available.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    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.
        // 臨時變量,進行和閾值判斷,給初始值 -1 不知有什麼特殊含義
        int c = -1;
        // 構造一個新節點
        Node<E> node = new Node<E>(e);
        // 獲取 putLock 鎖對象
        final ReentrantLock putLock = this.putLock;
        // 獲取 count 對象
        final AtomicInteger count = this.count;
        // 首先獲取 putLock 的鎖,保證 putLock 的線程安全性
        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.
             */
            // 若是隊列達到閾值,則阻塞等待,當消費者消費的時候會調用 notFull.signal()
            while (count.get() == capacity) {
                notFull.await();
            }
            // 入隊操作
            enqueue(node);
            // count 先賦值給臨時變量 c,然後自增加 1
            c = count.getAndIncrement();
            // 當前線程入隊成功,釋放 notFull 信號量
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            // 最後要釋放鎖
            putLock.unlock();
        }
        // 隊列空到不空的狀態,釋放 notEmpty 信號量,主要是喚起消費者進行消費(出隊)
        if (c == 0)
            signalNotEmpty();
    }
    
    
    /**
     * Signals a waiting take. Called only from put/offer (which do not
     * otherwise ordinarily lock takeLock.)
     * 
     * 釋放 notEmpty 信號量,喚起消費者消費,這個方法是從 put 和 offer 方法進行調用的
     * 
     */
    private void signalNotEmpty() {
        // 獲取 takeLock 鎖對象
        final ReentrantLock takeLock = this.takeLock;
        // 獲取鎖
        takeLock.lock();
        try {
            // 釋放 notEmpty 信號量
            notEmpty.signal();
        } finally {
            // 釋放 takeLock 鎖
            takeLock.unlock();
        }
    }
 
  • 入隊(非阻塞),並返回入隊結果: public boolean offer(E e)


    /**
     * Inserts the specified element at the tail of this queue if it is
     * possible to do so immediately without exceeding the queue's capacity,
     * returning {@code true} upon success and {@code false} if this queue
     * is full.
     * When using a capacity-restricted queue, this method is generally
     * preferable to method {@link BlockingQueue#add add}, which can fail to
     * insert an element only by throwing an exception.
     *
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        // 這特麼都不等,要是隊列滿了,直接返回失敗
        if (count.get() == capacity)
            return false;
        // 這裏賦值爲 -1 還是有點意義的,要是入隊失敗,在返回結果時 c>=0 爲 false
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            // 這邊沒有進行阻塞操作,所以,可能再多線程(也就是說在 putLock.lock(); 之前正好最後一個元素進入導致隊滿)的情況下,最後一個位置存在競爭導致入隊失敗。
            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;
    }
    
    
  • 入隊(等待超時),並設置超時時間,並返回入隊結果:public boolean offer(E e, long timeout, TimeUnit unit)

  /**
     * Inserts the specified element at the tail of this queue, waiting if
     * necessary up to the specified wait time for space to become available.
     *
     * @return {@code true} if successful, or {@code false} if
     *         the specified waiting time elapses before space is available
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     * 
     * 參數依次代表的含義是 入隊信息,超時時間,時間類型
     */
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // 轉換時間爲毫微秒(NANOSECONDS)
        long nanos = unit.toNanos(timeout);
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
                // 若超時,返回 false 表示入隊未成功
                if (nanos <= 0)
                    return false;
                // 獲取本次循環後的剩餘時間
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(new Node<E>(e));
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        // 入隊成功則返回 true 
        return true;
    }
    
  • 出隊(阻塞):public E take()

    public E take() throws InterruptedException {
        // 聲明結果
        E x;
        // 臨時變量 c 存儲隊列數目變化,進行判斷是否釋放 notEmpty 信號量
        int c = -1;
        // 獲取 隊列中元素
        final AtomicInteger count = this.count;
        // 獲取 takeLock 鎖對象
        final ReentrantLock takeLock = this.takeLock;
        // 獲取 takeLock 鎖
        takeLock.lockInterruptibly();
        try {
            // 隊列爲空,則阻塞,等待生產者(入隊成功)釋放信號量
            while (count.get() == 0) {
                notEmpty.await();
            }
            // 出隊操作
            x = dequeue();
            // 首先將 count 賦值給 c, 然後 count 自減
            c = count.getAndDecrement();
            // 若在移除之前不爲空,則釋放 notEmpty 信號量,可進行出隊操作
            if (c > 1)
                notEmpty.signal();
        } finally {
            // 釋放 takeLock 鎖
            takeLock.unlock();
        }
        // 閾值的元素已出隊,隊列從滿到不滿的狀態,說明可釋放入隊的信號量
        if (c == capacity)
            signalNotFull();
        // 返回出隊元素內容
        return x;
    }

    /**
     * Removes a node from head of queue.
     *
     * @return the node
     */
    private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == 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;
    }
    /**
     * Signals a waiting put. Called only from take/poll.
     * 原先隊列已滿,達到閾值,調用此方法說明已經出隊了一個元素,釋放 notFull 信號,通知生產者可入隊。
     */
    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        // 獲取 putLock 鎖
        putLock.lock();
        try {
            // 釋放 notFull 信號量
            notFull.signal();
        } finally {
            // 釋放鎖
            putLock.unlock();
        }
    }
    
  • 出隊(不阻塞):public E poll()


   public E poll() {
        final AtomicInteger count = this.count;
        // 若隊空,則直接返回 null
        if (count.get() == 0)
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            // 隊列不爲空方可進行出隊操作
            if (count.get() > 0) {
                x = dequeue();
                c = count.getAndDecrement();
                if (c > 1)
                    notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
    
    
  • 出隊(等待超時):public E poll(long timeout, TimeUnit unit)

   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 {
            // 進行自旋
            while (count.get() == 0) {
                // 超時後返回 null,表示隊空
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
    
    
  • 獲取隊頭元素,但不出隊:public E peek()

    public E peek() {
        // 隊空沒元素可獲取 
        if (count.get() == 0)
            return null;
        // 獲取 takeLock 鎖對象
        final ReentrantLock takeLock = this.takeLock;
        // 獲取 takeLock 鎖
        takeLock.lock();
        try {
            // 第一個元素,並返回結果
            Node<E> first = head.next;
            if (first == null)
                return null;
            else
                return first.item;
        } finally {
            // 釋放鎖
            takeLock.unlock();
        }
    }

  • 移除元素:public boolean remove(Object o)

/**
     * Removes a single instance of the specified element from this queue,
     * if it is present.  More formally, removes an element {@code e} such
     * that {@code o.equals(e)}, if this queue contains one or more such
     * elements.
     * Returns {@code true} if this queue contained the specified element
     * (or equivalently, if this queue changed as a result of the call).
     *
     * @param o element to be removed from this queue, if present
     * @return {@code true} if this queue changed as a result of the call
     */
    public boolean remove(Object o) {
        // 元素爲空,直接返回 false 
        if (o == null) return false;
        // 移除操作需要鎖住出入隊操作
        fullyLock();
        try {
            // 進行遍歷節點查找匹配的位置
            for (Node<E> trail = head, p = trail.next;
                 p != null;
                 trail = p, p = p.next) {
                // 若是查詢到元素
                if (o.equals(p.item)) {
                    // 刪除節點操作
                    unlink(p, trail);
                    // 返回成功
                    return true;
                }
            }
            // 返回 false
            return false;
        } finally {
            // 釋放出入隊鎖
            fullyUnlock();
        }
    }

    /**
     * Locks to prevent both puts and takes.
     * 
     * 先鎖住入隊,可有效利用資源
     */
    void fullyLock() {
        // 入隊鎖
        putLock.lock();
        // 出隊鎖
        takeLock.lock();
    }        

    /**
     * Unlocks to allow both puts and takes.
     * 先釋放出隊鎖,有利於釋放資源
     */
    void fullyUnlock() {
        // 釋放出隊鎖
        takeLock.unlock();
        // 釋放入隊鎖
        putLock.unlock();
    }        
 
        /**
         * Unlinks interior Node p with predecessor trail.
         * 刪除節點 p
         */
        void unlink(Node<E> p, Node<E> trail) {
            // assert isFullyLocked();
            // p.next is not changed, to allow iterators that are
            // traversing p to maintain their weak-consistency guarantee.
            p.item = null;
            // 直接將 p 的前驅節點的後繼節點設置爲 p 的後繼節點
            trail.next = p.next;
            // 若 p 爲隊尾,需要將隊尾指針指向 p 的前驅節點
            if (last == p)
                last = trail;
            // 若是正好從閾值刪除一個節點,需要喚起入隊操作
            if (count.getAndDecrement() == capacity)
                notFull.signal();
        }
    
  • 包含元素:public boolean contains(Object o)

    /**
     * Returns {@code true} if this queue contains the specified element.
     * More formally, returns {@code true} if and only if this queue contains
     * at least one element {@code e} such that {@code o.equals(e)}.
     *
     * @param o object to be checked for containment in this queue
     * @return {@code true} if this queue contains the specified element
     */
    public boolean contains(Object o) {
        // null 則無須查找
        if (o == null) return false;
        // 需要鎖住出入隊操作,若是一直都有出入隊操作,可能會造成死循環
        fullyLock();
        try {
            // 遍歷節點查找匹配的元素,若查詢到則返回 true,否則返回 false
            for (Node<E> p = head.next; p != null; p = p.next)
                if (o.equals(p.item))
                    return true;
            return false;
        } finally {
            // 釋放出入隊操作
            fullyUnlock();
        }
    }
    
  • 轉換成數組:public Object[] toArray()

    /**
     * Returns an array containing all of the elements in this queue, in
     * proper sequence.
     *
     * <p>The returned array will be "safe" in that no references to it are
     * maintained by this queue.  (In other words, this method must allocate
     * a new array).  The caller is thus free to modify the returned array.
     *
     * <p>This method acts as bridge between array-based and collection-based
     * APIs.
     *
     * @return an array containing all of the elements in this queue
     */
    public Object[] toArray() {
        // 鎖住出入隊操作
        fullyLock();
        try {
            // 獲取隊列大小
            int size = count.get();
            // 聲明結果數組
            Object[] a = new Object[size];
            int k = 0;
            // 遍歷隊列中每一個節點,然後賦值給數組
            for (Node<E> p = head.next; p != null; p = p.next)
                a[k++] = p.item;
            // 返回結果數組
            return a;
        } finally {
            // 釋放出入隊操作
            fullyUnlock();
        }
    }
    
    
  • 轉換成指定類型的數組:public T[] toArray(T[] a)

 /**
     * Returns an array containing all of the elements in this queue, in
     * proper sequence; the runtime type of the returned array is that of
     * the specified array.  If the queue fits in the specified array, it
     * is returned therein.  Otherwise, a new array is allocated with the
     * runtime type of the specified array and the size of this queue.
     *
     * <p>If this queue fits in the specified array with room to spare
     * (i.e., the array has more elements than this queue), the element in
     * the array immediately following the end of the queue is set to
     * {@code null}.
     *
     * <p>Like the {@link #toArray()} method, this method acts as bridge between
     * array-based and collection-based APIs.  Further, this method allows
     * precise control over the runtime type of the output array, and may,
     * under certain circumstances, be used to save allocation costs.
     *
     * <p>Suppose {@code x} is a queue known to contain only strings.
     * The following code can be used to dump the queue into a newly
     * allocated array of {@code String}:
     *
     *  <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
     *
     * Note that {@code toArray(new Object[0])} is identical in function to
     * {@code toArray()}.
     *
     * @param a the array into which the elements of the queue are to
     *          be stored, if it is big enough; otherwise, a new array of the
     *          same runtime type is allocated for this purpose
     * @return an array containing all of the elements in this queue
     * @throws ArrayStoreException if the runtime type of the specified array
     *         is not a supertype of the runtime type of every element in
     *         this queue
     * @throws NullPointerException if the specified array is null
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        fullyLock();
        try {
            int size = count.get();
            // 傳入的數組容量不夠,這邊似乎有個風險,若 a 爲 null 則會出現空指針異常,因爲底層擴容是 native 方法,所以這邊加了 unchecked ,註釋上也說明了會有空指針的情況
            if (a.length < size)
                a = (T[])java.lang.reflect.Array.newInstance
                    (a.getClass().getComponentType(), size);

            int k = 0;
            // 遍歷隊並一一賦值
            for (Node<E> p = head.next; p != null; p = p.next)
                a[k++] = (T)p.item;
            // 這種情況可能出現在 a.length 大於 count.get 的情況,一般數組是連續的空間,我不清楚爲啥不將之後的元素都設爲 null,這個是很有意思的點
            if (a.length > k)
                a[k] = null;
            return a;
        } finally {
            fullyUnlock();
        }
    }
    
    
  • 轉換成字符串: public String toString()

  • 置空隊列: public void clear()

    /**
     * Atomically removes all of the elements from this queue.
     * The queue will be empty after this call returns.
     */
    public void clear() {
        fullyLock();
        try {
            // 遍歷節點,輔助 GC,這裏面 p 負責清空節點裏面元素,h 負責指針指向,二者相互協助輔助 GC
            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();
        }
    }
    
  • 全部出隊:public int drainTo(Collection<? super E> c)

    /**
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     * @throws IllegalArgumentException      {@inheritDoc}
     * 
     * 這個沒啥好說的
     */
    public int drainTo(Collection<? super E> c) {
        return drainTo(c, Integer.MAX_VALUE);
    }

  • 批量出隊,指定大小:public int drainTo(Collection<? super E> c, int maxElements)

   /**
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     * @throws IllegalArgumentException      {@inheritDoc}
     */
    public int drainTo(Collection<? super E> c, int maxElements) {
        // c 不能爲空
        if (c == null)
            throw new NullPointerException();
        // 不能爲本身
        if (c == this)
            throw new IllegalArgumentException();
        // 出隊數量要合法
        if (maxElements <= 0)
            return 0;
        // 判斷是否需要釋放入隊的信號量
        boolean signalNotFull = false;
        // 獲取 takeLock 對象
        final ReentrantLock takeLock = this.takeLock;
        // 獲取鎖
        takeLock.lock();
        try {
            // 獲取實際能出隊的數量
            int n = Math.min(maxElements, count.get());
            // count.get provides visibility to first n Nodes
            // 獲取頭節點
            Node<E> h = head;
            // 記錄位置
            int i = 0;
            try {
                // 進行遍歷。出隊操作
                while (i < n) {
                    // 從頭節點下一個節點開始獲取元素
                    Node<E> p = h.next;
                    // 添加到集合
                    c.add(p.item);
                    // 以下是輔助 GC
                    p.item = null;
                    h.next = h;
                    h = p;
                    // 計數加一
                    ++i;
                }
                // 返回數量
                return n;
            } finally {
                // Restore invariants even if c.add() threw
                if (i > 0) {
                    // assert h.item == null;
                    head = h;
                    // 判斷是否隊滿
                    signalNotFull = (count.getAndAdd(-i) == capacity);
                }
            }
        } finally {
            // 釋放出隊的鎖
            takeLock.unlock();
            // 爲啥在這個裏面進行釋放非隊滿信號?寫在這是因爲程序的嚴謹性,出隊鎖完成纔算完整的出隊操作結束,這樣才能釋放非滿隊列信號,喚醒生產者進行生產。
            if (signalNotFull)
                signalNotFull();
        }
    }

  • 獲取迭代子: public Iterator iterator()

    /**
     * Returns an iterator over the elements in this queue in proper sequence.
     * The elements will be returned in order from first (head) to last (tail).
     *
     * <p>The returned iterator is
     * <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
     *
     * @return an iterator over the elements in this queue in proper sequence
     *  直接聲明一個 繼承 Iterator 的內部類並返回
     */
    public Iterator<E> iterator() {
        return new Itr();
    }
    
 private class Itr implements Iterator<E> {
        /*
         * Basic weakly-consistent iterator.  At all times hold the next
         * item to hand out so that if hasNext() reports true, we will
         * still have it to return even if lost race with a take etc.
         */
        /**
         * 當前節點
         */
        private Node<E> current;
        /**
         * 記錄指針移動前的節點
         */
        private Node<E> lastRet;
        /**
         * 當前元素
         */
        private E currentElement;

        Itr() {
            // 鎖出出入隊操作
            fullyLock();
            try {
                // 獲取第一個節點
                current = head.next;
                // 獲取第一個節點元素
                if (current != null)
                    currentElement = current.item;
            } finally {
                // 釋放出入隊鎖
                fullyUnlock();
            }
        }
       
        public boolean hasNext() {
            // 判斷是否還有下一個節點.爲啥不是 current.next!=null?聯繫下 remove就知道問題所在
            return current != null;
        }

        /**
         * Returns the next live successor of p, or null if no such.
         *
         * Unlike other traversal methods, iterators need to handle both:
         * - dequeued nodes (p.next == p)
         * - (possibly multiple) interior removed nodes (p.item == null)
         */
        private Node<E> nextNode(Node<E> p) {
            for (;;) {
                Node<E> s = p.next;
                // 若在迭代器中執行了 clear() 方法會出現這種情況
                if (s == p)
                    return head.next;
                // 若 p 爲 隊尾,next 指向 null ,若不是隊尾,返回下一個元素
                if (s == null || s.item != null)
                    return s;
                p = s;
            }
        }

        public E next() {
            // 鎖出出入隊操作
            fullyLock();
            try {
                // 節點爲空
                if (current == null)
                    throw new NoSuchElementException();
                // 當前的元素
                E x = currentElement;
                // 記錄當前節點,用於刪除操作
                lastRet = current;
                // 指針向後繼節點移動
                current = nextNode(current);
                // 獲取後繼節點的元素
                currentElement = (current == null) ? null : current.item;
                // 返回結果
                return x;
            } finally {
                // 釋放出入隊鎖
                fullyUnlock();
            }
        }

        public void remove() {
            // 因此在執行 remove 方法之前必須執行 next()
            if (lastRet == null)
                throw new IllegalStateException();
            // 鎖住出入隊操作
            fullyLock();
            try {
                // 獲取節點
                Node<E> node = lastRet;
                // 刪除後置爲空,保證不能重複刪除
                lastRet = null;
                // 這個和 remove 方法類似,不多說了
                for (Node<E> trail = head, p = trail.next;
                     p != null;
                     trail = p, p = p.next) {
                    if (p == node) {
                        unlink(p, trail);
                        break;
                    }
                }
            } finally {
                fullyUnlock();
            }
        }
    }    
    
  • 分割迭代器:public Spliterator spliterator()

  • 序列化隊列:private void writeObject(java.io.ObjectOutputStream s)

  • 反序列化隊列:private void readObject(java.io.ObjectInputStream s)

解讀 LinkedBlockingQueue 遇到的問題

  • 入隊的時候,需要注意隊列從空隊轉換成非空隊,出隊的時候,需要注意從隊滿轉換成非隊滿,這是爲什麼?

因爲當調用 take 的時候,空隊會阻塞等待,空隊->非空隊,需要釋放非空信號量,通知消費者消費;同理調用 put 的時候,隊滿則阻塞等待,隊滿->非隊滿,需要釋放非滿信號量,通知生產者生產。

  • 在 offer 方法的時候,之前已經判斷達到閾值則返回 false 表示失敗,爲什麼在入隊之前還判斷是否達到閾值?

這步操作是爲了線程的安全,在競爭大的情況下,判斷是否達到閾值到獲取到 putLock 鎖中間可能會有入隊成功並達到隊滿的情況,所以必須在入隊之前判斷是否達到閾值。

  • fullyLock 方法入隊鎖在前,出隊鎖在後,而 fullyUnlock 則是釋放出隊鎖在前,釋放入隊鎖在後,why?

因爲這樣能更好的利用系統資源,入隊是在佔用資源,出隊是在釋放資源。

  • 在 contains 不涉及到出入隊操作,爲什麼也需要限制出入隊?

說到底還是保證原子性,防止在多線程的情況刪除了某個值或者添加了某個值導致和預期不一致。

  • toArray(T[] a) 方法中爲什麼只將緊跟其後的元素置爲空,後續數組空間爲啥不用處理?

註釋上只是說了這樣操作,但沒有說這種情況,理論上我們也不會做這種傻事。這個原因阿導也在思考…

  • 批量出隊 drainTo(Collection<? super E> c, int maxElements) 方法中,爲什麼釋放非滿信號量要放在釋放出隊鎖之後

寫在這是因爲程序的嚴謹性,出隊鎖完成纔算完整的出隊操作結束,這樣才能釋放非滿隊列信號,喚醒生產者進行生產。

  • 觀察下面代碼,這是迭代器 next 中調用的一個方法,目的是獲取下一個節點,請問什麼情況下會出現 s==p 的情況


        private Node<E> nextNode(Node<E> p) {
            for (;;) {
                Node<E> s = p.next;
                // ?
                if (s == p)
                    return head.next;
                if (s == null || s.item != null)
                    return s;
                p = s;
            }
        }
        
        

顯然,當 s!=null&& s.item == null&& p=p.next 纔會發生,也就是空隊的時候纔會出現,但是在獲取迭代子的時候然後進行判斷是否有後續節點 hasNext 方法的時候爲 false,執行 next 會報 NoSuchElementException 異常。
因此出現這種情況必然是 hasNext 爲 true 的情況下然後調用了 clear() 方法將隊列置空後再調用 next() 方法纔會出現 s==p 的情況。

發佈了79 篇原創文章 · 獲贊 12 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章