Java併發編程之BlockingQueue阻塞隊列--------介紹,分類,源碼分析

一.簡介

以下摘自jdk1.8源碼:

A {@link java.util.Queue} that additionally supports operations
that wait for the queue to become non-empty when retrieving an
element, and wait for space to become available in the queue when
storing an element.

  • 一個隊列
  • 這個隊列要求在隊列非空時取出元素
  • 這個隊列要求在隊列非滿時存儲元素

總結:

阻塞隊列是一個隊列。試圖從空的隊列中獲取元素的線程將會被阻塞,直到其他線程往空的隊列插入新的元素。試圖向已滿的隊列中添加新元素的線程將會被阻塞,直到其他線程從隊列中移除一個或多個元素或者完全清空,使隊列變得空閒起來並後續新增


二.源碼:

BlockingQueue是一個接口:

public interface BlockingQueue<E> extends Queue<E> 

實現Queue接口,這是一個隊列結構:先進先出。
在這裏插入圖片描述
BlockingQueue的實現類很多
在這裏插入圖片描述
常用的有ArrayBlockingQueue,LinkedBlockingQueue, SynchronousQueue,以下舉例使用ArrayBlockingQueue。


ArrayBlockingQueue 分析

先看一下聲明部分

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
        
    //和ArrayList一樣,底層是用的數組
    final Object[] items;

	/** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;

注意有兩個Condition一個表示非空,一個表示非滿,還有一個Lock

再看一下核心的方法

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();
        }
    }

/** Condition for waiting puts */
 private final Condition notFull;

可以看出put方法的邏輯也是通過Condition配合ReentrantLock實現的,當count和數組長度items.length相等,說明隊列滿,則調用Conditon的await;否則說明有空位,調用enqueue()方法:

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();
    }

前幾行都是比較常見的添加元素的方法,注意最後要調用Condition的signal方法,喚醒在notEmpty這個condition上阻塞的線程


take:

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

take的邏輯和put基本一樣,當Count=0,當前condition(notEmpty)阻塞,否則返回dequeue(),最後通知生產者,喚醒生產者線程(在notFull處阻塞)

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex]; //取到E
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
		/**
		@key jdk1.8的新特性迭代器特性,這裏是因爲元素的出隊列所以清理和這個元素相關聯的迭代器
		*/
        if (itrs != null) 
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

三.阻塞隊列的應用


在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚起
  • 爲什麼需要BlockingQueue 。好處是我們不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因爲這一切BlockingQueue都給你一手包辦了
  • 在concurrent包發佈以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的複雜度。
  • 在生產者消費者模型中,生產數據和消費數據的速率不一致,如果生產數據速度快一些,消費(處理)不過來,就會導致數據丟失。這時候我們就可以應用上阻塞隊列來解決這個問題。

四.API分類

方法類型 拋出異常 特殊值 阻塞 超時
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take poll(time,unit)
檢查 element() peek() 不可用 不可用


方法類型 status
拋出異常 當阻塞隊列滿時,再往隊列中add會拋IllegalStateException: Queue full
當阻塞隊列空時,在網隊列裏remove會拋NoSuchElementException
特殊值 插入方法,成功true失敗false
移除方法,成功返回出隊列的元素,隊列裏沒有就返回null
一直阻塞 當阻塞隊列滿時,生產者線程繼續往隊列裏put元素,隊列會一直阻塞線程知道put數據或響應中斷退出
當阻塞隊列空時,消費者線程試圖從隊列take元素,隊列會一直阻塞消費者線程知道隊列可用。
超時退出 當阻塞隊列滿時,隊列會阻塞生產者線程一定時間,超過限時後生產者線程會退出
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章