Java集合(20)——併發集合(8)——阻塞隊列(1)——ArrayBlockingQueue源碼分析

目錄

1.概述

2.使用案例

3.源碼分析

3.1 重要屬性

3.2 構造方法

3.3 私有方法入隊與出隊

(1)入隊

(2)出隊

3.4 put和take方法

(1)put

(2)take

3.5 offer和poll

(1)offer

(2)poll

3.6 peek

3.7 remainingCapacity

3.8  remove

4.總結


1.概述

  • ArrayBlockingQueue 是 BlockingQueue 接口的有界阻塞隊列實現類,底層採用數組來實現。

特點

  • ArrayBlockingQueue一旦創建,容量不能改變。
  • 其併發控制採用可重入鎖來控制,不管是插入操作還是讀取操作,都需要獲取到鎖才能進行操作。
  • ArrayBlockingQueue 默認情況下不能保證線程訪問隊列的公平性,
    • 所謂公平性是指嚴格按照線程等待的絕對時間順序,即最先等待的線程能夠最先訪問到 ArrayBlockingQueue。
    • 而非公平性則是指訪問 ArrayBlockingQueue 的順序不是遵守嚴格的時間順序,有可能存在,

當 ArrayBlockingQueue 可以被訪問時,長時間阻塞的線程依然無法訪問到 ArrayBlockingQueue。如果保證公平性,通常會降低吞吐量。如果需要獲得公平性的 ArrayBlockingQueue,可採用如下代碼:

private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);

2.使用案例

使用案例見上一篇博客:https://blog.csdn.net/qq_34805255/article/details/101924873

 

3.源碼分析

3.1 重要屬性

public class ArrayBlockingQueue {

    /**
     * ArrayBlockingQueue的底層實現爲此數組
     *
     * 並且可以看到此數組被final修飾,數組一旦創建,容量不可變(數組本身的性質),並且,數組引用指向的數組對象不可變
     */
    final Object[] items;

    /**
     * 隊首索引位置
     */
    int takeIndex;

    /**
     * 隊尾索引位置
     */
    int putIndex;

    /**
     * 隊列中元素的個數
     */
    int count;


    /**
     * 採用ReentrantLock來保證線程安全
     */
    final ReentrantLock lock;

    /**
     * 爲了保證消費(take)數據的時候如果爲空的時候,進行阻塞(等待),使用了Condition
     *
     * 當獲取數據的消費者線程被阻塞時會將該線程放置到notEmpty等待隊列中(notEmpty可以理解爲等待隊列不空的等待隊列)
     */
    private final Condition notEmpty;

    /**
     * 爲了保證生產(put)數據的時候如果隊列滿的時候,進行阻塞(等待),使用了Condition
     *
     * 當插入數據的生產者線程被阻塞時,會將該線程放置到notFull等待隊列中(notFull可以理解爲等待隊列不滿的等待隊列)。
     */
    private final Condition notFull;

}
  • 底層採用final數組items實現
  • 使用鎖ReentrantLock保證線程安全
  • 使用兩個Condition來實現阻塞

3.2 構造方法

    /**
     * 我們在創建一個ArrayBlockingQueue時,必須傳入容量用做創建數組的大小
     * 
     * 當我們不傳入是否使用公平鎖時,默認是非公平的
     */
    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();
    }
  • 構造函數就是初始化了一些屬性,如數組,鎖,兩個條件隊列

3.3 私有方法入隊與出隊

(1)入隊

    /**
     * 讓元素入隊
     */
    private void enqueue(E x) {

        final Object[] items = this.items;
        //插入數據到隊尾
        items[putIndex] = x;
        //如果隊尾剛好是數組的最後一個位置,那麼,插入後隊尾位置變成第一個位置
        if (++putIndex == items.length)
            putIndex = 0;
        //插入後,元素數量加1
        count++;
        //通知被阻塞的消費者線程
        notEmpty.signal();
    }
  • 通過上述的可以發現:
    • ArrayBlockingQueue的底層是使用數組實現了一個環形隊列,使用takeIndex標誌隊列的隊首位置,使用putIndex標誌隊列的隊尾位置
    • 當隊尾到達數組的最後一個位置時,插入後的下一個隊尾位置又會回到開頭
  • 它是如何判斷隊列滿的呢?
    • 我們在put和offer方法中都看到,即爲判斷隊列滿的語句,只有這個條件不滿足,即隊列不滿,纔會調用私有方法enqueue

(2)出隊

    /**
     * 讓元素出隊
     */
    private E dequeue() {

        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        //先用臨時變量保存隊首元素
        E x = (E) items[takeIndex];
        //然後將隊首引用置空
        items[takeIndex] = null;
        ////如果隊首剛好是數組的最後一個位置,那麼,刪除元素後隊首位置變成第一個位置
        if (++takeIndex == items.length)
            takeIndex = 0;
        //隊首元素出隊後,元素數量減1
        count--;

        if (itrs != null)
            itrs.elementDequeued();
        //通知被阻塞的生產者線程
        notFull.signal();
        return x;
    }

dequeue方法也主要做了兩件事情:

  • 1. 獲取隊列中的數據,即獲取數組中的數據元素((E) items[takeIndex]),刪除隊首元素的實現是通過讓原來隊首引用指向null,然後讓該引用原來指向的堆中的元素被GC回收
  • 2. 通知notFull等待隊列中的線程,使其由等待隊列移入到同步隊列中,使其能夠有機會獲得lock,並執行完成功退出。

3.4 put和take方法

(1)put

    /**
     * 阻塞插入:
     *      即插入時,在隊列滿的時候阻塞,在隊列不滿的時候直接插入
     */
    public void put(E e) throws InterruptedException {
        //檢查傳入的元素不爲空
        checkNotNull(e);

        final ReentrantLock lock = this.lock;

        //加可中斷鎖
        lock.lockInterruptibly();
        try {
            //當隊列滿的時候,阻塞
            while (count == items.length)
                notFull.await();
            //當隊列不滿的時候,入隊
            enqueue(e);
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

(2)take

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //當隊列空的時候,阻塞
            while (count == 0)
                notEmpty.await();
            //當隊列不空的時候,直接返回隊首元素
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

3.5 offer和poll

(1)offer

    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        //與put不同的是加的是lock
        lock.lock();
        try {
            //隊列滿的時候,直接返回false,表示插入不成功
            if (count == items.length)
                return false;
            //隊列不滿的時候,入隊插入元素,返回true,表示插入成功
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

(2)poll

    public E poll() {
        final ReentrantLock lock = this.lock;
        //使用lock保證線程安全
        lock.lock();
        try {
            //隊列爲空,返回null,否則出隊隊首元素,並返回
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

3.6 peek

    public E peek() {
        final ReentrantLock lock = this.lock;
        //通過lock保證線程安全
        lock.lock();
        try {
            //當隊列爲空的時候返回null,否則返回隊尾元素
            return itemAt(takeIndex); // null when queue is empty
        } finally {
            lock.unlock();
        }
    }

    /**
     * 當隊列爲空的時候,該索引位置的值爲null,所以會返回null
     */
    final E itemAt(int i) {
        return (E) items[i];
    }

3.7 remainingCapacity

    /**
     * 計算剩餘容量
     */
    public int remainingCapacity() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //加鎖計算剩餘的容量
            return items.length - count;
        } finally {
            lock.unlock();
        }
    }

3.8  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 int putIndex = this.putIndex;
                int i = takeIndex;
                //從隊首開始遍歷隊列
                do {
                    //如果傳入的元素值和該索引位置的該元素相等,則移除該元素返回true
                    if (o.equals(items[i])) {
                        removeAt(i);
                        return true;
                    }
                    //在遍歷過程中,遍歷指針i到最後一個位置時,它的下一個位置迭代爲0,否則++就行
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

4.總結

  • 可以發現ArrayBlockingQueue基本上所有的public方法都使用獨佔鎖ReentrantLock的對象lock進行加鎖來保證線程安全
  • 僅僅在put和take方法的時候加的是可中斷獨佔鎖
  • 使用兩個Condition的等待(await)-通知(signalAll)機制來實現阻塞,使用數組實現環形隊列
  • 對於 ArrayBlockingQueue,我們可以在構造的時候指定以下三個參數:

    • 隊列容量,其限制了隊列中最多允許的元素個數,即數組的大小
    • 指定獨佔鎖是公平鎖還是非公平鎖。非公平鎖的吞吐量比較高,公平鎖可以保證每次都是等待最久的線程獲取到鎖;
    • 可以指定用一個集合來初始化,將此集合中的元素在構造方法期間就先添加到隊列中。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章