concurrent包阻塞隊列詳解

     在編程中我們經常會使用到Queue容器類,但是這些容器類不是線程安全的,因此concurrent包中Doug Lea大師
爲我們準備了對應的線程安全的容器類;每一種容器類也滿足了對應的使用場景;那些本文就是梳理這些併發的Queue容
器類及使用場景;

 

1、主要容器實現類

         ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue

 

2、常用的BlockingQueue基本操作


插入操作:BlockingQueue實現的3和4

  1. add(E e) :往隊列插入數據,當隊列滿時,插入元素時會拋出IllegalStateException異常;
  2. offer(E e):當往隊列插入數據時,插入成功返回true,否則則返回false。當隊列滿時不會拋出異常;
  3. put:當阻塞隊列容量已經滿時,往阻塞隊列插入數據的線程會被阻塞,直至阻塞隊列已經有空餘的容量可供使用;
  4. offer(E e, long timeout, TimeUnit unit):若阻塞隊列已經滿時,同樣會阻塞插入數據的線程,直至阻塞隊列已經有空餘的地方,與put方法不同的是,該方法會有一個超時時間,若超過當前給定的超時時間,插入數據的線程會退出;

刪除操作BlockingQueue實現的3和4

  1. remove(Object o):從隊列中刪除數據,成功則返回true,否則爲false
  2. poll:刪除數據,當隊列爲空時,返回null;
  3. take():當阻塞隊列爲空時,獲取隊頭數據的線程會被阻塞;
  4. poll(long timeout, TimeUnit unit):當阻塞隊列爲空時,獲取數據的線程會被阻塞,另外,如果被阻塞的線程超過了給定的時長,該線程會退出

查看操作

  1. element:獲取隊頭元素,如果隊列爲空時則拋出NoSuchElementException異常;
  2. peek:獲取隊頭元素,如果隊列爲空則拋出NoSuchElementException異常

 

3、常用BlockingQueue詳解

   ①ArrayBlockingQueue 是由數組實現的有界阻塞隊列,隊列命令元素FIFO(先進先出);當隊列容量滿時放入元素操作會阻塞,當隊列爲null時獲取一個元素也會阻塞;

   默認情況下這種隊列不能保證訪問隊列的公平性,這是爲了提升吞吐量;確保公平性可以通過在構造函數中增加true設定;

 

        ②LinkedBlockingQueue 是用鏈表實現的有界阻塞隊列,也是FIFO特性;具有更高的吞吐量;  爲了防止迅速增加耗損大量的內容,通常創建對象時會指定其初始大小,未指定則爲Interger.MAX_VALUE

 

        ③PriorityBlockingQueue 是一個支持優先級的無界阻塞隊列,元素採用自然順序進行排序,可以通過自定義類實現compareTo()方法來指定元素排序規則,或者初始化時通過構造器參數Comparator指定排序規則

 

        ④SynchrousQueue 不存儲任何元素的阻塞隊列,只有當其他線程刪除數據才能插入數據,可以通過構造器參數來指定公平性;

 

   ⑤LinkedTransferQueue 由鏈表數據結構構成的無界阻塞隊列,實現了TransferQueue接口,相比其他阻塞隊列有以下不同方法:

transfer(E e)
如果當前有線程(消費者)正在調用take()方法或者可延時的poll()方法進行消費數據時,生產者線程可以調用transfer方法將數據傳遞給消費者線程。如果當前沒有消費者線程的話,生產者線程就會將數據插入到隊尾,直到有消費者能夠進行消費才能退出;

tryTransfer(E e)
tryTransfer方法如果當前有消費者線程(調用take方法或者具有超時特性的poll方法)正在消費數據的話,該方法可以將數據立即傳送給消費者線程,如果當前沒有消費者線程消費數據的話,就立即返回false。因此,與transfer方法相比,transfer方法是必須等到有消費者線程消費數據時,生產者線程才能夠返回。而tryTransfer方法能夠立即返回結果退出。

tryTransfer(E e,long timeout,imeUnit unit)</br>
與transfer基本功能一樣,只是增加了超時特性,如果數據才規定的超時時間內沒有消費者進行消費的話,就返回false

        ⑥LinkedBlockingDeque 基於鏈表數據結構的有界阻塞雙端隊列,在創建對象時爲指定大小時,其默認大小爲Integer.MAX_VALUE,

        ⑦DelayQueue 是一個存放實現Delayed接口的數據的無界阻塞隊列,只有當數據對象的延時時間達到時才能插入到隊列進行存儲;  當前多有的數據都還沒有達到創建時所指定的延時期,則隊列沒有隊頭,並且線程通過poll等方法獲取數據元素則返回null。所謂數據延時期滿時,則是通過Delayed接口的getDelay(TimeUnit.NANOSECONDS)來進行判定,如果該方法返回的是小於等於0則說明該數據元素的延時期已滿。

 

 

4,ArrayBlockingQueue源碼解析

  使用案例:通過ArrayBlockingQueue實現消費者-生產者模式

package com.hezm.thread.day1.collections;

import java.util.concurrent.ArrayBlockingQueue;

public class BlockingDemo {

    /***
    * 利用 ArrayBlockingQueue 的添加已滿會阻塞和獲取已空會阻塞實現生產者-消費者模式
    *
    */
    ArrayBlockingQueue<String> ab = new ArrayBlockingQueue(10);

    public void init() {
        new Thread(()->{
            try {
                String data = ab.take(); //阻塞方式獲取數據;
                System.out.println("receive:" + data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    public void addData(String data) {
        ab.add(data);
        try {
            System.out.println("send:" + data);
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        BlockingDemo blockingDemo = new BlockingDemo();
        for (int i = 0; i < 1000; i++) {
            blockingDemo.addData("data:" + i);
            blockingDemo.init();
        }
    }
}

     運行結果:

send:data:0
send:data:1
receive:data:0
send:data:2
receive:data:1
send:data:3
receive:data:2
send:data:4
receive:data:3
send:data:5
receive:data:4
send:data:6
receive:data:5
send:data:7
receive:data:6

 

      源碼分析:

/** 
* 關鍵元素: notEmpty 不爲null喚醒 、  notFull 不爲滿喚醒
*     天然滿足生產者-消費者:在爲null的時候消費者阻塞等待,有值之後消費者被喚醒;
*        在隊列滿的時候生產者阻塞等待,不滿的時候喚醒生產; 
*
*add{
* offer(){
* 重入鎖{ enqueue(e) 插入隊列
* if (++putIndex == items.length) putIndex = 0; //當要插入的數組下標滿了則重置爲0;
* notEmpty.signal(); //去喚醒 take中的 notEmpty.await()
* }
* }
* }
*
*
* take{
* 重入鎖中獲取可中斷的鎖;
* notEmpty.await() 阻塞等待隊列中設置進值
* dequeue() 出隊列;{
* if (++takeIndex == items.length) takeIndex = 0; 獲取隊列的數組下標;
* itrs.elementDequeued(); // 維護迭代器中的內容;
* notFull.signal();
* }
* }
* remove() 移除
*/

//add()   往隊列添加元素
   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();
        }
    }

    
     private void enqueue(E x) {   //入隊列
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)  //putIndex 當插入下標已滿則重置爲0,循環使用;
            putIndex = 0;
        count++;
        notEmpty.signal();    //非null喚醒;
    }



//take()  阻塞等待隊列數據進行消費
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();        //重入鎖-可中斷的鎖獲取
        try {
            while (count == 0)
                notEmpty.await();    //當隊列數爲0時非null等待
            return dequeue();     //出隊列
        } finally {
            lock.unlock();
        }
    }


    private E dequeue() {
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)   //出隊已滿則重置讀取下標爲0;
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();              //未滿隊列等待
        return x;
    }

 

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