Android併發學習之阻塞佇列

大頭呆發表於2018-06-14

簡介

多執行緒環境中,通過佇列可以很容易實現資料共享,比如經典的“生產者”和“消費者”模型中,通過佇列可以很便利地實現兩者之間的資料共享。假設我們有若干生產者執行緒,另外又有若干個消費者執行緒。如果生產者執行緒需要把準備好的資料共享給消費者執行緒,利用佇列的方式來傳遞資料,就可以很方便地解決他們之間的資料共享問題。但如果生產者和消費者在某個時間段內,萬一發生資料處理速度不匹配的情況呢?理想情況下,如果生產者產出資料的速度大於消費者消費的速度,並且當生產出來的資料累積到一定程度的時候,那麼生產者必須暫停等待一下(阻塞生產者執行緒),以便等待消費者執行緒把累積的資料處理完畢,反之亦然。

BlockingQueue很好地解決了上述問題,BlockingQueue即阻塞佇列,它是一個介面,它的實現類有ArrayBlockingQueue、DelayQueue、 LinkedBlockingDeque、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等,它們的區別主要體現在儲存結構上或對元素操作上的不同。

Android併發學習之阻塞佇列

常用方法

public interface BlockingQueue<E> extends Queue<E> {

    //往佇列尾部新增元素,如果BlockingQueue可以容納,則返回true,否則丟擲異常
    boolean add(E e);
    
   //移除元素,如果有這個元素則就回true,否則丟擲異常 
    boolean remove(Object o);
     
    //往佇列尾部新增元素,如果BlockingQueue可以容納則返回true,否則返回false.
    //如果是往限定了長度的佇列中設定值,推薦使用offer()方法。
    boolean offer(E e);
    
   //和上面的方法差不多,不過如果佇列滿了可以阻塞等待一段時間
    boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
    
    //取出頭部物件,若不能立即取出,則可以等time引數規定的時間,取不到時返回null
    E poll(long timeout, TimeUnit unit) throws InterruptedException; 
    
     //往佇列尾部新增元素,如果沒有空間,則呼叫此方法的執行緒被阻塞直到有空間再繼續.  
    void put(E e) throws InterruptedException;
    
    //取出頭部物件,若BlockingQueue為空,阻斷進入等待狀態直到Blocking有新的物件被加入為止 
    E take() throws InterruptedException;
    
    //剩餘容量,超出此容量,便無法無阻塞地新增元素
    int remainingCapacity();
    
    //判斷佇列中是否擁有該值。
    boolean contains(Object o);
    
    //一次性從BlockingQueue獲取所有可用的資料物件,可以提升獲取資料效率
    int drainTo(Collection<? super E> c);
    
     //和上面的方法差不多,不過限制了最大取出數量
    int drainTo(Collection<? super E> c, int maxElements);
    
}
複製程式碼

原始碼簡析

我們以ArrayBlockingQueue為例分析下上述方法:

offer(E e)

public boolean offer(E e) {
        Objects.requireNonNull(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;
        count++;
        notEmpty.signal();
    }
複製程式碼

offer操作如上,程式碼比較簡單,可見阻塞佇列是通過可重入保證執行緒安全。enqueue方法也說明了ArrayBlockingQueue是通過陣列的形式儲存資料的。如果佇列滿了直接會返回false,不會阻塞執行緒。

put(E e)

public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)//佇列滿了,一直阻塞在這裡
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

複製程式碼

因為put方法在佇列已滿的情況下會阻塞執行緒,take、poll等方法會呼叫dequeue方法出列,從而呼叫notFull.signal(),從而喚醒阻塞在put方法中執行緒去繼續進行入列操作:

take()

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            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) takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }
複製程式碼

poll(long timeout, TimeUnit unit)

從對頭取出一個元素:如果陣列不空,出隊;如果陣列已空且已經超時,返回null;如果陣列已空則進入等待,直到被喚醒或超時:

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);////將時間轉換為納秒
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {//佇列為空
                if (nanos <= 0L)
                    return null;
                //阻塞指定時間,enqueue()方法會呼叫notEmpty.signal()喚醒進行poll操作的執行緒
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
複製程式碼

參考文章和擴充套件閱讀

雖然只講了阻塞佇列,但涉及了ReentrantLock、中斷、Condition等知識點,如果不清楚的話可以看下下面的幾篇文章:

blog.csdn.net/vernonzheng…

wsmajunfeng.iteye.com/blog/162935…

Lock鎖和Condition條件

ava中Lock,tryLock,lockInterruptibly有什麼區別?

相關文章