BlockingQueue實現類 LinkedBlockingQueue源碼分析

LinkedBlockingQueue實現了BlockingQueue接口以及Serializable接口,是有序的FIFO隊列,構造函數中,可傳入一個最大容量值,如果沒有傳入,則默認是Integer.MAX_VALUE

一 首先看一下重要的幾個類變量:

   /** 保存當前隊列中元素的個數 */  
        private final AtomicInteger count = new AtomicInteger(0);  

        /** 
         * 頭元素 
         * Invariant: head.item == null 
         */  
        private transient Node<E> head;  

        /** 
         * 尾元素 
         * Invariant: last.next == null 
         */  
        private transient Node<E> last;  

        /** 消費者鎖,Lock held by take, poll, etc */  
        private final ReentrantLock takeLock = new ReentrantLock();  

        /** 使消費者線程等待,直到被喚醒或者打斷 */  
        private final Condition notEmpty = takeLock.newCondition();  

        /** 生產者鎖,Lock held by put, offer, etc */  
        private final ReentrantLock putLock = new ReentrantLock();  

        /** 使生產者線程等待,直到被喚醒或者打斷 */  
        private final Condition notFull = putLock.newCondition();  

二 put方法解讀

    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);  
            final ReentrantLock putLock = this.putLock;  
            final AtomicInteger count = this.count;  
            putLock.lockInterruptibly();  
            try {  
                  while (count.get() == capacity) {  
                    notFull.await();  
                }  
                enqueue(node);  
                c = count.getAndIncrement();  
                if (c + 1 < capacity)  
                    notFull.signal();  
            } finally {  
                putLock.unlock();  
            }  
            if (c == 0)  
                signalNotEmpty();  
        }  

執行過程如下:

1 如果傳入元素爲空,拋出空指針異常
2 獲得put的鎖,以及原子的count,然後lock,注意,是可打斷的lock
3 判斷當前隊列是否飽和,若飽和,生產者線程進入等待狀態
4 如果隊列不飽和,則將元素包裝爲一個node放到隊列中
5 count+1,如果count+1仍然小於隊列的最大容量,則生產者線程被喚醒
6 在finnally中釋放鎖,最後喚醒消費者,提醒消費者可以從隊列中取對象了

三 offer方法

offer提供了兩個方法,一個方法是可傳入等待時間,另一個則沒有

     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 {  
                while (count.get() == capacity) {  
                    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();  
            return true;  
        }  

執行過程如下:
1 如果傳入元素爲空,拋出空指針異常
2 根據傳入的timeout和unit計算出最長等待的時間
3 獲得put的鎖,以及原子的count,表示當前隊列中的元素個數,然後lock,注意,是可打斷的lock
4 如果隊列飽和,則超過等待時間後,直接返回false
5 以下處理構成同put

另外一個不帶參數的方法,如果判斷隊列飽和,直接返回false,不阻塞

四 take方法

java代碼

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

執行過程如下:
1 獲得put的鎖,以及原子的count,表示當前隊列中的元素個數,然後lock,注意,是可打斷的lock
2 如果當前隊列爲空,則阻塞
3 如果非空,則元素出列,count-1,如果count>1,表示隊列非空,則消費者線程被喚醒
4 在finally中釋放lock,喚醒生產者生產

五 pool方法

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

執行過程如下:
1 獲得put的鎖,以及原子的count,表示當前隊列中的元素個數,然後lock,注意,是可打斷的lock
2 如果當前隊列爲空,則線程阻塞,並等待指定時間,如果在指定時間還是空,則直接返回空

3 以下過程同take

pool也提供了不帶參數的方法,表示如果隊列爲空,則直接返回null,不阻塞

六 peek

 public E peek() {  
            if (count.get() == 0)  
                return null;  
            final ReentrantLock takeLock = this.takeLock;  
            takeLock.lock();  
            try {  
                Node<E> first = head.next;  
                if (first == null)  
                    return null;  
                else  
                    return first.item;  
            } finally {  
                takeLock.unlock();  
            }  
        }  

不阻塞,不移除隊首元素

個人學習總結:

根據以上源碼分析,得出方法之間的異同:
(1) 生產者方法:put,offer
(2)消費者方法:take,poll,peek
(3put方法中,如果隊列始終飽和,則當前線程會一直等待,直到有對象出列
     offer(可傳入等待時間),如果隊列飽滿,會等待指定的時間,如果在指定時間內還飽滿,則直接返回false
     offer(沒有參數),如果隊列飽滿,直接返回false,線程不等待
     put和offer(可傳入等待時間)都是可被打斷的

 (4)take在隊列爲空時,會始終阻塞
          poll分爲帶等待時間和不帶的,如果不帶等待時間,則不阻塞,移除隊首元素(FIFO)
          peek是不移除隊首元素,不阻塞
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章