併發編程系列之阻塞隊列(BlockingQueue)

前言

上節我們介紹了非阻塞隊列ConcurrentLinkedQueue的相關內容,今天我們再來說說Java中的阻塞隊列BlockingQueue,主要介紹下阻塞隊列的概念,常見的阻塞隊列,以及阻塞隊列的底層實現。

 

什麼是阻塞隊列?

阻塞隊列就是一種支持阻塞的插入和移除操作的特殊容器

  • 阻塞的插入:當隊列滿時,向隊列中插入元素的線程會被阻塞,直到隊列中有元素被移除,即隊列不滿時,阻塞的線程才能繼續向隊列中插入元素;

  • 阻塞的移除:當隊列中沒有元素時,即隊列爲空時,從隊列中移除元素的線程就會被阻塞,直到隊列中有新的元素被添加,即隊列中有元素時,阻塞的線程才能繼續從隊列中移除元素;

阻塞隊列的常見操作如下:

 

常見的幾種阻塞隊列

BlockingQueue是一個接口,主要有下面7種實現類

  • ArrayBlockingQueue:基於數組的阻塞隊列實現,在其內部,維護了一個定長數組,以便緩存隊列中的數據對象,其內部沒實現讀寫分離,也就意味着生產和消費不能完全並行,長度是需要自己定義的,可以指定先進先出或者先進後出,也被稱爲“有界隊列”

  • LinkedBlockingQueue:基於鏈表的阻塞隊列,跟ArrayBlockingQueue類似,其內部也維持着一個數據緩衝隊列(該隊列由一個鏈表構成),LinkedBlockingQueue之所以能夠高效的處理併發數據,是因爲其內部實現採用分離鎖(讀寫分離兩個鎖),從而實現生產者和消費者操作完全併發執行,也是一個“無界隊列”

  • PriorityBlockingQueue:基於優先級的阻塞隊列(優先級的判斷通過構造函數傳入的Compator(比較器)對象決定,也就是說傳入隊列的對象必須實現Comparable接口),在實現PriorityBlockingQueue時,內部控制線程同步的鎖採用的是公平鎖,是一個“無界隊列”(PriorityBlockingQueue調用take後需要重新排序,調一次重新排一次)

  • DelayQueue:帶有延遲時間的無界阻塞Queue,其中的元素只有當指定的延遲時間到了,才能夠從隊列中獲取該元素。DelayQueue中的元素必須實現Delayed接口,DelayQueue是一個沒有大小限制的隊列,應用場景比較多,比如對緩存超時的數據進行移除,任務超時處理,空間連接的關閉等等

  • SynchronousQueue:不存儲任何元素的隊列,生產者產生的數據直接會被消費者獲取並消費,即沒一個put操作必須等待一個take操作,否則不能繼續添加元素,或者你可以理解爲是隻能存儲一個元素的隊列,存一個就滿了,該元素必須被移除掉,才能繼續添加

  • LinkedTransferQueue:一個由鏈表結構組成的無界阻塞傳輸隊列,主要體現在LinkedTransferQueue多2個方法:

    • transfer(E):如果當前有消費者正在等待消費,則生產者直接把元素傳輸給消費者,如果當前沒有消費者正在等待消費,則生產者將元素存放在隊列的tail節點上,並等到該元素被消費才返回(採用自旋等待);

    • tryTransfer(E):將元素立刻傳輸給一個等待接收元素的線程,如果沒有消費者就會返回false,而不將元素放入隊列;

    • tryTransfer(E,long,TimeUnit):將元素立刻給消費者,如果沒有消費者就等待指定時間。時間到時,如果還沒有消費者則失敗返回false;

  • LinkedBlockingDeque:由鏈表結構組成的雙向阻塞隊列,即可以從隊列的兩端插入和移出元素

 

阻塞隊列的底層實現

阻塞隊列的底層主要使用的還是之前我們介紹過得等待通知機制來實現的,等待通知機制在阻塞隊列中具體體現爲如下思想:當生產者往一個滿隊列中添加元素時,生產者會被阻塞,當消費者從該隊列中消費了一個元素後,會通知阻塞的插入操作的生產者線程,告訴它當前隊列不滿,可以繼續執行添加操作。我們通過下面源碼可以更好的理解這一點:

final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;

public ArrayBlockingQueue(int capacity, boolean fair) {
       if (capacity <= 0)
           throw new IllegalArgumentException();
       this.items = new Object[capacity];
       lock = new ReentrantLock(fair);
       // 使用condition模式等待通知
       notEmpty = lock.newCondition();
       notFull =  lock.newCondition();
   }

   public void put(E e) throws InterruptedException {
       checkNotNull(e);
       final ReentrantLock lock = this.lock;
       lock.lockInterruptibly();
       try {
           while (count == items.length)
               // 當隊列滿時,阻塞
               notFull.await();
           // 否則繼續添加元素
           insert(e);
       } finally {
           lock.unlock();
       }
   }

private void insert(E x) {
       items[putIndex] = x;
       putIndex = inc(putIndex);
       ++count;
       // 添加元素時會喚醒等待移出數據的take線程
       notEmpty.signal();
   }

public E take() throws InterruptedException {
       final ReentrantLock lock = this.lock;
       lock.lockInterruptibly();
       try {
           while (count == 0)
               // 當隊列爲空時,獲取數據的線程等待
               notEmpty.await();
           // 否則就取出元素,並且喚醒等待的put線程
           return extract();
       } finally {
           lock.unlock();
       }
   }

   private E extract() {
       final Object[] items = this.items;
       E x = this.<E>cast(items[takeIndex]);
       items[takeIndex] = null;
       takeIndex = inc(takeIndex);
       --count;
       // 喚醒等待的put線程
       notFull.signal();
       return x;
   }

 

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