多線程之間的交互:阻塞隊列之ArrayBlockingQueue

ArrayBlockingQueue概念

基於數組的阻塞隊列實現,在ArrayBlockingQueue內部,維護了一個定長數組,以便緩存隊列中的數據對象,這是一個常用的阻塞隊列,除了一個定長數組外,ArrayBlockingQueue內部還保存着兩個整形變量,分別標識着隊列的頭部和尾部在數組中的位置。隊列的頭部是在隊列中存在時間最長的元素。對列的尾部是在隊列中存在時間最短的元素。新元素插入到隊列的尾部,隊列獲取操作則是從隊列的頭部獲取元素的。
這是一個典型的“有界緩存區”,固定大小的數組在其中保持生產者插入的元素和消費者提取的元素。一段創建了這樣的緩存區,就不能再增加其容量。試圖向已滿的隊列中放入元素會導致操作受阻塞;試圖從空隊列中提取元素將導致類似受阻塞。
此類支持對等待的生產者線程和使用者線程進行排序的可選公平策略(FIFO)。默認情況下是非公平策略(LIFO)。
然而,通過將公平性(fairness)設置爲true而構造的隊列允許按照FIFO順序訪問線程。公平性會降低吞吐量。
ArrayBlockingQueue在生產者放入數據和消費者獲取數據,都是共用同一個鎖對象,由此也意味着兩者無法真正並行運行,這點尤其不同於LinkedBlockingQueue。

先進先出(FIFO):先插入的隊列的元素也最先出隊列,類似於排隊的功能。從某種程度上來說這種隊列也體現了一種公平性。
後進先出(LIFO):後插入隊列的元素最先出隊列,這種隊列優先處理最近發生的事件。

ArrayBlockingQueue部分源碼

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    //數組的存儲結構
    final Object[] items;

    //鎖採用的機制
    final ReentrantLock lock;

    //只限制長度的隊列,起公平性設置了false
    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();
    }

    //生產者放入元素到隊列中,若隊列已滿,返回false
    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

    //對列已滿則受阻塞
    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();
        }
    }

    //消費者獲取元素,對列爲空時受阻塞
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } 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();
    }
}

應用示例

需求:
* 現有的程序代碼模擬產生了16個日誌對象,並且需要運行16秒才能打印完這些日誌,
* 請在程序中增加4個線程去調用parseLog()方法分頭打印這16個日誌對象,
* 程序只需運行4s即可完成打印這新日誌對象

package test;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(16);

        //創建四個線程去取,並調用打印方法,觀察是否爲先進先出:使用take方式讀取,若隊列爲空則阻塞,直至隊列有值
        for(int i=0;i<4;i++){
            new Thread(
                    new Runnable() {
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            while(true){
                                try {
                                    String log = bq.take();
                                    parseLog(log);
                                } catch (InterruptedException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
            ).start();
        }

        //將日誌全部放在隊列中。使用put--隊列滿了處於阻塞狀態,直至隊列可用
        for(int i=0;i<16;i++){
            String log = (i+1)+"------>";
            try {
                System.out.println("put :"+log);
                bq.put(log);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }


    public static void parseLog(String log){
        System.out.println(log + System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

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