Queue常用類解析之BlockingQueue(二):ArrayBlockingQueue

Queue常用類解析之PriorityQueue
Queue常用類解析之ConcurrentLinkedQueue
Queue常用類解析之BlockingQueue(一):PriorityBlockingQueue、DelayQueue和DelayedWorkQueue

接着上文對BlockingQueue的介紹繼續向下

五、ArrayBlockingQueue

從命名可以看出,這是一個循環數組表示的的阻塞隊列。
與前面介紹的BlockingQueue不同,ArrayBlockingQueue在入隊和出隊時都有可能會陷入阻塞。

1. 屬性

/** The queued items */
final Object[] items;

/** items index for next take, poll, peek or remove */
int takeIndex;

/** items index for next put, offer, or add */
int putIndex;

/** Number of elements in the queue */
int count;

/*
 * Concurrency control uses the classic two-condition algorithm
 * found in any textbook.
 */

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

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

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

/**
 * Shared state for currently active iterators, or null if there
 * are known not to be any.  Allows queue operations to update
 * iterator state.
 */
transient Itrs itrs = null;

putIndex和takeIndex分別表示入隊和出隊的數組索引。
notEmpty和notFull分別表示空隊列和滿隊列時的阻塞condition。
Itrs 時迭代器的鏈表形式的集合。

2. 構造器

public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

ArrayBlockingQueue由兩個構造器方法,不支持無參構造器。至少需要傳入隊列的容量,並初始化對於長度的數組。後續數組的長度無法修改。另外,ArrayBlockingQueue還支持在構造器方法中傳入是否是公平鎖的參數,默認是非公平鎖。

3. ArrayBlockingQueue#put(Object)

public void put(E e) throws InterruptedException {
	//元素不能爲null
    checkNotNull(e);
    //加鎖
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
    	//滿隊列,線程阻塞
        while (count == items.length)
            notFull.await();
         //入隊操作
        enqueue(e);
    } finally {
        lock.unlock();
    }
}
private void enqueue(E x) {
   // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    //putIndex入隊的數組索引
    items[putIndex] = x;
    //循環數組,到達數組末尾後的下一個索引爲0
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    //發送notEmpty信號喚醒
    notEmpty.signal();
}

4. ArrayBlockingQueue#poll(long, TimeUnit)

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 <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        //執行出隊操作
        return dequeue();
    } finally {
        lock.unlock();
    }
}
private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    //takeIndex出隊的數組索引
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    //循環數組,達到數組末尾的下一個元素是0
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    //itrs操作
    if (itrs != null)
        itrs.elementDequeued();
     //發送notFull信號喚醒線程
    notFull.signal();
    return x;
}

5. ArrayBlockingQueue#remove(Object)

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;
            //從takeIndex開始遍歷,直到putIndex - 1
            do {
            	//相等,執行刪除邏輯
                if (o.equals(items[i])) {
                    removeAt(i);
                    return true;
                }
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);
        }
        return false;
    } finally {
        lock.unlock();
    }
}
void removeAt(final int removeIndex) {
    // assert lock.getHoldCount() == 1;
    // assert items[removeIndex] != null;
    // assert removeIndex >= 0 && removeIndex < items.length;
    final Object[] items = this.items;
    //相當於poll
    if (removeIndex == takeIndex) {
        // removing front item; just advance
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    } else {
        // an "interior" remove

        // slide over all others up through putIndex.
        final int putIndex = this.putIndex;
        //從removeIndex到putIndex - 1的元素依次向前挪動一位,並putIndex = putIndex - 1
        for (int i = removeIndex;;) {
            int next = i + 1;
            if (next == items.length)
                next = 0;
            if (next != putIndex) {
                items[i] = items[next];
                i = next;
            } else {
                items[i] = null;
                this.putIndex = i;
                break;
            }
        }
        count--;
        //itrs處理
        if (itrs != null)
            itrs.removedAt(removeIndex);
    }
    //發送信號
    notFull.signal();
}

6. Itrs

Itr是ArrayBlockingQueue的迭代器類,而Itrs則是由Itr組成的鏈表集合類。
對於Itrs和Itr,因爲循環數組和移除元素時會使迭代器丟失他們的位置,爲了保證迭代器和隊列數據的一致性,需要注意
(1)記錄所有takeIndex循環到0的次數
(2)每次刪除元素都需要通過removeAt方法通知到所有的迭代器。
Itrs中使用的是Itr的弱引用類,因此需要針對Itrs中的過期迭代器進行清理。清理過期節點主要有3個時機:
(1)建立了新的迭代器,調用doSomeSweep
(2)takeIndex循環到0,調用takeIndexWrapped
(3)隊列變爲空隊列時,調用queueIsEmpty

6.1 屬性

/** Incremented whenever takeIndex wraps around to 0 */
//takeIndex 循環到0的次數
int cycles = 0;

/** Linked list of weak iterator references */
//頭結點
private Node head;

/** Used to expunge stale iterators */
//清理過期迭代器的開始節點
private Node sweeper = null;

//清理過期迭代器時的節點檢查數量,harder模式時16,否則4
private static final int SHORT_SWEEP_PROBES = 4;
private static final int LONG_SWEEP_PROBES = 16;

6.2 Itrs#register(Itr)

void register(Itr itr) {
   // assert lock.getHoldCount() == 1;
    head = new Node(itr, head);
}

迭代器加入Itrs鏈表,頭插法。

6.3 Itrs#doSomeSweep(boolean)

void doSomeSweeping(boolean tryHarder) {
    // assert lock.getHoldCount() == 1;
    // assert head != null;
    int probes = tryHarder ? LONG_SWEEP_PROBES : SHORT_SWEEP_PROBES;
    Node o, p;
    final Node sweeper = this.sweeper;
    //passGo表示已經從頭結點開始檢查
    boolean passedGo;   // to limit search to one full sweep

    if (sweeper == null) {
        o = null;
        p = head;
        passedGo = true;
    } else {
        o = sweeper;
        p = o.next;
        passedGo = false;
    }

    for (; probes > 0; probes--) {
        if (p == null) {
        	//已經檢查到尾節點,並且檢查是從頭節點開始的,跳出循環
            if (passedGo)
                break;
            //已經檢查到尾節點,但沒有檢查過頭結點,從頭節點開始再次檢查,並記錄passedGo=true
            o = null;
            p = head;
            passedGo = true;
        }
        final Itr it = p.get();
        final Node next = p.next;
        //過期的無效節點
        if (it == null || it.isDetached()) {
            // found a discarded/exhausted iterator
            //一旦找到一個過期的節點,就會採用harder模式檢查更多的節點。
            probes = LONG_SWEEP_PROBES; // "try harder"
            // unlink p
            p.clear();
            p.next = null;
            if (o == null) {
                head = next;
                //鏈表中已經沒有迭代器
                if (next == null) {
                    // We've run out of iterators to track; retire
                    itrs = null;
                    return;
                }
            }
            else
                o.next = next;
        } else {
            o = p;
        }
        p = next;
    }

    //記錄sweeper節點,下一次清理直接從sweeper 開始
    this.sweeper = (p == null) ? null : o;
}

該方法用於檢查並清理過期節點,參數爲是否採用harder模式,harder模式檢查16個節點,非harder模式檢查4個節點。
一旦找到一個過期的節點,就會採用harder模式檢查更多的節點。
檢查並清理的動作在循環中進行,檢查結束的條件由以下3個,滿足其一就可以離開循環。
(1)檢查的節點數量達到要求
(2)鏈表已經完整了檢查了一遍。
(3)鏈表中已經沒有迭代器

6.4 Itrs#takeIndexWrapped()

void takeIndexWrapped() {
    // assert lock.getHoldCount() == 1;
    cycles++;
    for (Node o = null, p = head; p != null;) {
        final Itr it = p.get();
        final Node next = p.next;
        if (it == null || it.takeIndexWrapped()) {
            // unlink p
            // assert it == null || it.isDetached();
            p.clear();
            p.next = null;
            if (o == null)
                head = next;
            else
                o.next = next;
        } else {
            o = p;
        }
        p = next;
    }
    if (head == null)   // no more iterators to track
        itrs = null;
}

takeIndex每次循環到0時會調用該方法。
cycle計數增加,遍歷鏈表檢查並清理過期的無效節點。

6.5 Itrs#queueIsEmpty()

void queueIsEmpty() {
    // assert lock.getHoldCount() == 1;
    for (Node p = head; p != null; p = p.next) {
        Itr it = p.get();
        if (it != null) {
            p.clear();
            it.shutdown();
        }
    }
    head = null;
    itrs = null;
}

隊列每次變爲空隊列時調用,清空所有有效的迭代器。

6.6 Itrs#elementDequeued()

void elementDequeued() {
    // assert lock.getHoldCount() == 1;
    if (count == 0)
        queueIsEmpty();
    else if (takeIndex == 0)
        takeIndexWrapped();
}

在ArrayBlockingQueue中移除元素(包括出隊和remove)時調用,保證迭代器和隊列數據的一致性。

7. Itr

Itr是ArrayBlockingQueue的迭代器,所有迭代器都會記錄到對應ArrayBlockingQueue的itrs屬性中。
Itr是一個先讀後寫的迭代器,會先預讀到next的值進行保存,即使後續next對應的元素被移除也能通過迭代器的next方法訪問到。這是爲了防止hasNext方法執行和next方法執行的之間的某個時刻,該節點被刪除,導致hasNext返回true而next返回值卻爲null的情形,因此ArrayBlockingQueue的迭代器Itr是弱一致性的。
當迭代器的所有下標都爲負數或者hasNext第一次返回false時進入detach狀態,表示迭代器已經無用,可以從itrs中移除。

7.1 屬性

/** Index to look for new nextItem; NONE at end */
//遊標,下一個next節點對應的下標,到達putIndex結束的位置爲NONE
private int cursor;

/** Element to be returned by next call to next(); null if none */
//next節點的元素值
private E nextItem;

/** Index of nextItem; NONE if none, REMOVED if removed elsewhere */
//next節點的下標
private int nextIndex;

/** Last element returned; null if none or not detached. */
//上一個節點的元素值
private E lastItem;

/** Index of lastItem, NONE if none, REMOVED if removed elsewhere */
//上一個節點的下標
private int lastRet;

/** Previous value of takeIndex, or DETACHED when detached */
//記錄takeIndex
private int prevTakeIndex;

/** Previous value of iters.cycles */
//記錄cycles 
private int prevCycles;

/** Special index value indicating "not available" or "undefined" */
private static final int NONE = -1;

/**
 * Special index value indicating "removed elsewhere", that is,
 * removed by some operation other than a call to this.remove().
 */
private static final int REMOVED = -2;

/** Special value for prevTakeIndex indicating "detached mode" */
private static final int DETACHED = -3;

可以看到,每次都會預讀到next的值存儲到nextItem和nextIndex,並且保存上一次的返回值lastItem和lastRet。
由於ArrayBlockingQueue時一個循環數組,takeIndex和putIndex一直在循環移動。這樣子就有可能出現,迭代器創建時位置已經確定,但是隨着ArrayBlockingQueue數組不斷循環,位置一直在變化,導致迭代器的結果有誤。因此需要記錄舊的takeIndex和cycles,在遍歷時比較這兩個值與當前值,以修正下標索引,保證遍歷的正確性。

7.2 Itr#Itr()

Itr() {
    // assert lock.getHoldCount() == 0;
    lastRet = NONE;
    final ReentrantLock lock = ArrayBlockingQueue.this.lock;
    lock.lock();
    try {
    	//沒有元素,無用的迭代器,直接進入detach模式
        if (count == 0) {
            // assert itrs == null;
            cursor = NONE;
            nextIndex = NONE;
            prevTakeIndex = DETACHED;
        } else {
        	//保存各項屬性
            final int takeIndex = ArrayBlockingQueue.this.takeIndex;
            prevTakeIndex = takeIndex;
            nextItem = itemAt(nextIndex = takeIndex);
            cursor = incCursor(takeIndex);
            //迭代器加入itrs
            if (itrs == null) {
                itrs = new Itrs(this);
            } else {
                itrs.register(this); // in this order
                itrs.doSomeSweeping(false);
            }
            prevCycles = itrs.cycles;
            // assert takeIndex >= 0;
            // assert prevTakeIndex == takeIndex;
            // assert nextIndex >= 0;
            // assert nextItem != null;
        }
    } finally {
        lock.unlock();
    }
}

7.3 Itr#incorporateDequeues()

private void incorporateDequeues() {
    // assert lock.getHoldCount() == 1;
    // assert itrs != null;
    // assert !isDetached();
    // assert count > 0;

    final int cycles = itrs.cycles;
    final int takeIndex = ArrayBlockingQueue.this.takeIndex;
    final int prevCycles = this.prevCycles;
    final int prevTakeIndex = this.prevTakeIndex;

	//cycles和takeIndex存在不一致,需要修正
    if (cycles != prevCycles || takeIndex != prevTakeIndex) {
        final int len = items.length;
        // how far takeIndex has advanced since the previous
        // operation of this iterator
        //計算出隊了元素數量
        long dequeues = (cycles - prevCycles) * len
            + (takeIndex - prevTakeIndex);

        // Check indices for invalidation
        //校驗下標合法性,lastRet 和 nextIndex 無效記錄被移除,cursor無效那麼下一個next讀取從takeIndex重新開始
        if (invalidated(lastRet, prevTakeIndex, dequeues, len))
            lastRet = REMOVED;
        if (invalidated(nextIndex, prevTakeIndex, dequeues, len))
            nextIndex = REMOVED;
        if (invalidated(cursor, prevTakeIndex, dequeues, len))
            cursor = takeIndex;

		//進入detach模式
        if (cursor < 0 && nextIndex < 0 && lastRet < 0)
            detach();
        else {
        	//記錄新的cycles和takeIndex
            this.prevCycles = cycles;
            this.prevTakeIndex = takeIndex;
        }
    }
}
//判斷下標合法性,無效返回true
//判斷邏輯,下標距離的prevTakeIndex元素數量 和 出隊元素數量 比較
private boolean invalidated(int index, int prevTakeIndex,
                           long dequeues, int length) {
    if (index < 0)
        return false;
    int distance = index - prevTakeIndex;
    if (distance < 0)
        distance += length;
    return dequeues > distance;
}

用於檢查並修正Itr的下標屬性,在noNext、next和remove方法中會被調用。

7.4 Itr#detach()

private void detach() {
    // Switch to detached mode
    // assert lock.getHoldCount() == 1;
    // assert cursor == NONE;
    // assert nextIndex < 0;
    // assert lastRet < 0 || nextItem == null;
    // assert lastRet < 0 ^ lastItem != null;
    if (prevTakeIndex >= 0) {
        // assert itrs != null;
        prevTakeIndex = DETACHED;
        // try to unlink from itrs (but not too hard)
        itrs.doSomeSweeping(true);
    }
}

修改detach的標誌字段,並且啓動itrs的清理邏輯。

7.5 Itr#hasNext()

public boolean hasNext() {
    // assert lock.getHoldCount() == 0;
    if (nextItem != null)
        return true;
    noNext();
    return false;
}
private void noNext() {
    final ReentrantLock lock = ArrayBlockingQueue.this.lock;
    lock.lock();
    try {
        // assert cursor == NONE;
        // assert nextIndex == NONE;
        if (!isDetached()) {
            // assert lastRet >= 0;
            incorporateDequeues(); // might update lastRet
            if (lastRet >= 0) {
                lastItem = itemAt(lastRet);
                // assert lastItem != null;
                detach();
            }
        }
        // assert isDetached();
        // assert lastRet < 0 ^ lastItem != null;
    } finally {
        lock.unlock();
    }
}

判斷下一個節點是否存在,由於下一個元素已經預讀取並保存在nextItem,判斷nextItem是否爲null即可。
對於沒有下一個節點的迭代器,需要修正下標屬性並進入detach模式。

7.6 Itr#next()

public E next() {
   // assert lock.getHoldCount() == 0;
    final E x = nextItem;
    if (x == null)
        throw new NoSuchElementException();
    final ReentrantLock lock = ArrayBlockingQueue.this.lock;
    lock.lock();
    try {
        if (!isDetached())
            incorporateDequeues();
        // assert nextIndex != NONE;
        // assert lastItem == null;
        lastRet = nextIndex;
        final int cursor = this.cursor;
        if (cursor >= 0) {
            nextItem = itemAt(nextIndex = cursor);
            // assert nextItem != null;
            this.cursor = incCursor(cursor);
        } else {
            nextIndex = NONE;
            nextItem = null;
        }
    } finally {
        lock.unlock();
    }
    return x;
}

由於下一個元素已經與讀取,因此next方法的主要邏輯不是讀取下一個元素,而是預讀取並保存下一個元素的下一個元素。

7.7 Itr#remove()

public void remove() {
   // assert lock.getHoldCount() == 0;
    final ReentrantLock lock = ArrayBlockingQueue.this.lock;
    lock.lock();
    try {
    	//非detach模式修正下標屬性
        if (!isDetached())
            incorporateDequeues(); // might update lastRet or detach
        final int lastRet = this.lastRet;
        //lastRest清空
        this.lastRet = NONE;
        if (lastRet >= 0) {
        	//非detach模式直接移除
            if (!isDetached())
                removeAt(lastRet);
            else {
            	//detach需要判斷lastItem是否發生了改變,才能移除
                final E lastItem = this.lastItem;
                // assert lastItem != null;
                this.lastItem = null;
                if (itemAt(lastRet) == lastItem)
                    removeAt(lastRet);
            }
        } else if (lastRet == NONE)
            throw new IllegalStateException();
        // else lastRet == REMOVED and the last returned element was
        // previously asynchronously removed via an operation other
        // than this.remove(), so nothing to do.

        if (cursor < 0 && nextIndex < 0)
        	//進入detach模式
            detach();
    } finally {
        lock.unlock();
        // assert lastRet == NONE;
        // assert lastItem == null;
    }
}

7.8 Itr#removeAt(int)

boolean removedAt(int removedIndex) {
    // assert lock.getHoldCount() == 1;
    if (isDetached())
        return true;

    final int cycles = itrs.cycles;
    final int takeIndex = ArrayBlockingQueue.this.takeIndex;
    final int prevCycles = this.prevCycles;
    final int prevTakeIndex = this.prevTakeIndex;
    final int len = items.length;
    int cycleDiff = cycles - prevCycles;
    if (removedIndex < takeIndex)
        cycleDiff++;
    final int removedDistance =
        (cycleDiff * len) + (removedIndex - prevTakeIndex);
    // assert removedDistance >= 0;
    int cursor = this.cursor;
    if (cursor >= 0) {
        int x = distance(cursor, prevTakeIndex, len);
        if (x == removedDistance) {
            if (cursor == putIndex)
                this.cursor = cursor = NONE;
        }
        else if (x > removedDistance) {
            // assert cursor != prevTakeIndex;
            this.cursor = cursor = dec(cursor);
        }
    }
    int lastRet = this.lastRet;
    if (lastRet >= 0) {
        int x = distance(lastRet, prevTakeIndex, len);
        if (x == removedDistance)
            this.lastRet = lastRet = REMOVED;
        else if (x > removedDistance)
            this.lastRet = lastRet = dec(lastRet);
    }
    int nextIndex = this.nextIndex;
    if (nextIndex >= 0) {
        int x = distance(nextIndex, prevTakeIndex, len);
        if (x == removedDistance)
            this.nextIndex = nextIndex = REMOVED;
        else if (x > removedDistance)
            this.nextIndex = nextIndex = dec(nextIndex);
    }
    else if (cursor < 0 && nextIndex < 0 && lastRet < 0) {
        this.prevTakeIndex = DETACHED;
        return true;
    }
    return false;
}

所有隊列移除非takeIndex下標處元素的方法都會調用迭代器的removeAt方法以通知其修正下標索引值。

7.9 Itr#takeIndexWrapped()

boolean takeIndexWrapped() {
    // assert lock.getHoldCount() == 1;
    if (isDetached())
        return true;
     //已經循環至少兩圈,迭代器的所有元素都已經無效
    if (itrs.cycles - prevCycles > 1) {
        // All the elements that existed at the time of the last
        // operation are gone, so abandon further iteration.
        shutdown();
        return true;
    }
    return false;
}
void shutdown() {
    // assert lock.getHoldCount() == 1;
    cursor = NONE;
    if (nextIndex >= 0)
        nextIndex = REMOVED;
    if (lastRet >= 0) {
        lastRet = REMOVED;
        lastItem = null;
    }
    prevTakeIndex = DETACHED;
    // Don't set nextItem to null because we must continue to be
    // able to return it on next().
    //
    // Caller will unlink from itrs when convenient.
}

當takeIndex循環到0時調用,對迭代器做一次檢查,如果必要則進入detach狀態,返回迭代器是否是detach狀態。

可以看到,進入detach模式雖然有兩種情況,標誌字段爲prevTakeIndex。
(1)所有索引都爲負數
(2)hasNext第一次返回false,關鍵是cursor < 0。
所以進入detach模式的關鍵有3種情況:
(1)cursor == putIndex,這時候cursor =NONE
(2)空隊列
(3)cycle - preCycles > 1

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