ArrayBlockingQueue

ArrayBlockingQueue閱讀筆記

一、簡介

​ 一種阻塞的隊列,添加和移除元素都會進行ReentrantLock加鎖,其中的take和put方法利用了condition條件鎖來讓添加和獲取可以讓線程等待有元素或有空位置再取或加

二、繼承關係圖

在這裏插入圖片描述

三、存儲結構

  • 不需要擴容。初始化指定了容量,並循環利用數組,加上添加和取出元素都加鎖

四、源碼分析

內部類

屬性

/** 用於存儲元素的對象數組 */
final Object[] items;

/** 取元素的指針 */
int takeIndex;

/** 放元素的指針 */
int putIndex;

/** 隊列中元素的數量 */
int count;

/** 保證併發訪問的鎖 */
final ReentrantLock lock;

/** 非空條件:用於隊列元素數量爲 0 時,take元素的等待條件鎖 */
private final Condition notEmpty;

/** 非滿條件:用於隊列元素數量達到最大時,put新的元素的等待鎖 */
private final Condition notFull;

構造

/** 構造方法一:初始化隊列最大容量 */
public ArrayBlockingQueue(int capacity) {
    // 默認非公平
    this(capacity, false);
}
/** 構造方法二:初始化隊列最大容量、公平/非公平模式選擇 */
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    // 初始化ReentrantLock鎖、非空條件鎖、非滿條件鎖
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}
/** 構造方法三:初始化隊列最大容量、公平/非公平模式選擇、初始化隊列元素集合 */
public ArrayBlockingQueue(int capacity, boolean fair,
                          Collection<? extends E> c) {
    // 調用構造方法二進行初始化
    this(capacity, fair); 
    final ReentrantLock lock = this.lock;
    // 加鎖:進行隊列元素初始化
    lock.lock(); // Lock only for visibility, not mutual exclusion
    try {
        int i = 0;//開始添加元素的索引
        try {
            for (E e : c) {
                // null檢查,如果是null拋NullPointerException異常
                checkNotNull(e);
                // 把元素加入到隊列中
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            // 如果初始化的元素數量超出 可添加元素數量則拋出IllegalArgumentException異常
            throw new IllegalArgumentException();
        }
        // 此時的i也是隊列數組中的元素數量,所以賦值給count
        count = i;
        // 也是下一次添加元素需要放置的位置
        // 如果 i 等於對象數組的大小,則把添加元素的指針放在索引 0,否則直接放在 i的位置
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}

主要方法

1、添加元素
  • add(E e),如果隊列滿了就拋出異常:IllegalStateException(“Queue full”)
  • offer(E e),如果隊列滿了返回false,
  • put(E e,long timeout,TimeUtil timeunit),如果隊列滿了則使用notFull等待
  • offer(E e),如果隊列滿了,則等待一算時間後 隊列中元素數量
// 添加元素,成功true,失敗會拋出異常
public boolean add(E e) {
    // 調用AbstractQueue.add(E e)方法
    return super.add(e);
}
// AbstractQueue.add(E e)
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

// 添加元素,成功true,失敗flase
public boolean offer(E e) {
    // 元素null檢查,如果爲null拋出異常
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 加鎖
    lock.lock();
    try {
        // 如果元素滿,則返回false,所以只有元素滿的情況纔會返回false
        if (count == items.length)
            return false;
        else {
            // 添加元素
            enqueue(e);
            return true;
        }
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}
public void put(E e) throws InterruptedException {
    // null 檢查
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 加鎖,如果線程中斷了拋出異常
    lock.lockInterruptibly();
    try {
        // 如果數組滿了,就是用notFull鎖等待
        // 等隊列取出一個元素的時候,再喚醒取元素
        while (count == items.length)
            notFull.await();
        // 把元素加入對象數組
        enqueue(e);
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    checkNotNull(e);
    // 獲取納秒
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 加鎖
    lock.lockInterruptibly();
    try {
        // 如果數組滿了,就阻塞nanos納秒
        // 如果喚醒這個線程時依然沒有空間且時間到了就返回flase
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        // 入隊
        enqueue(e);
        return true;
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}
// 把元素添加到putIndex,並把putIndex指向數組隊列下一個元素
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    // 引用當前集合的對象數組
    final Object[] items = this.items;
    // 把元素放在putIndex指針下
    items[putIndex] = x;
    // 如果放元素指針達到了盡頭,就重新指向第一個位置
    if (++putIndex == items.length)
        putIndex = 0;
    // 元素熟練 + 1
    count++;
    // 喚醒notEmpty鎖等待隊列中的線程,也就是取元素的等待鎖,告訴條件鎖,可以取元素了
    notEmpty.signal();
}
2、獲取元素
  • remove():如果隊列爲null則拋出異常

  • poll():如果隊列爲null則返回null

  • take(): 如果隊列爲null則阻塞等待在條件notEmpty的等待隊列

  • poll(long,TimeUnit):如果隊列爲null則阻塞等待納秒時間後如果還爲null就返回null

  • peek():利用取指針循環從數組中取出元素

/*
    雖然是刪除(是父類AbstractQueue的方法,但是通過模板模式調用了自身的poll()函數),但是也是調用的poll(),如果返回結果爲null,會拋出NoSuchElementException異常,有元素就返回對應元素
    */
public E remove() {
    // 直接調用poll
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
/*
	取出元素,如果沒有元素則返回null,否則返回取出元素對象(同時會喚醒notFull條件鎖),如果有元素,返回對應元素,並把takeIndex + 1
*/
public E poll() {
    final ReentrantLock lock = this.lock;
    // 加鎖
    lock.lock();
    try {
        // 如果元素爲0,則返回null,否則執行dequeue取出元素
        return (count == 0) ? null : dequeue();
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}

/*
	取出元素,如果元素中沒有對象,則直接把線程加入到notEmpty條件鎖鏈表隊列尾,返回對應元素,並把takeIndex + 1
*/
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 加鎖,如果線程中斷則拋出異常
    lock.lockInterruptibly();
    try {
        while (count == 0)
            // 如果隊列中沒有元素,則加入notEmpty等待隊列,等待添加新的元素喚醒
            notEmpty.await();
        // 取出元素,並喚醒notFull中的等待隊列first線程
        return dequeue();
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}

/*
	取出元素,如果隊列沒有元素,則會等待timeout納秒,如果在timeout納秒內還沒有獲取元素,就直接返回null,如果有元素則返回對應元素,並把takeIndex + 1
*/
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 加鎖,如果線程中斷則拋出異常
    lock.lockInterruptibly();
    try {
        // 如果隊列中沒有元素,則阻塞等待nanos秒
        // 如果下一次這個線程獲得了鎖但隊列依然無元素且已超時就返回null
        while (count == 0) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        // 擁有元素,直接調用取出元素
        return dequeue();
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}

/*
	獲取元素,有則返回元素,沒有返回null,此方法不會對元素移除(也就是改變takeIndex值)
*/
public E peek() {
    final ReentrantLock lock = this.lock;
    // 加鎖
    lock.lock();
    try {
        // 直接返回元素,如果沒有返回null
        return itemAt(takeIndex); // null when queue is empty
    } finally {
        // 解鎖
        lock.unlock();
    }
}
// 把takeIndex下的元素設置爲null,並把takeIndex指向數組隊列下一個元素 
private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];
    // 取出元素,並設置爲null
    items[takeIndex] = null;
    if (++takeIndex == items.length)
        // 取索引指向下一個元素
        // 如果更新後的取索引 超出元素下標,則設置爲0
        takeIndex = 0;
    // 元素個數 - 1
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // 喚醒notFull條件鎖中的first線程,notFull 隊列慢的時候的等待鎖
    notFull.signal();
    return x;
}
3、刪除元素
public boolean remove(Object o) {
    if (o == null) return false;
    final Object[] items = this.items;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count > 0) {
            final int putIndex = this.putIndex;
            int i = takeIndex;
            do {
                if (o.equals(items[i])) {
                    removeAt(i);
                    return true;
                }
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);
        }
        return false;
    } finally {
        lock.unlock();
    }
}

五、總結

  • 不需要擴容,初始化指定了容量
  • 添加和獲取元素都加鎖,利用了takeIndex和putIndex循環利用數組
  • 入隊和出隊定義了四種方法來滿足不同用途
發佈了48 篇原創文章 · 獲贊 17 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章