JUC源碼解析-阻塞隊列-迭代器(二)

接着上一篇,分析的是ArrayBlockingQueue的實現。

4,刪除 Itr#remove

        public void remove() {
            // assert lock.getHoldCount() == 0;
            final ReentrantLock lock = ArrayBlockingQueue.this.lock;
            lock.lock(); // 獲取鎖
            try {
                if (!isDetached())
                	// 修正下標,可能會更改 lastRet的值或detach迭代器
                    incorporateDequeues(); // might update lastRet or detach
                final int lastRet = this.lastRet;
                this.lastRet = NONE; // 置爲NONE
                if (lastRet >= 0) {
                    if (!isDetached())
                    	//刪除數組中lastRet位置元素
                        removeAt(lastRet);
                    // 若是迭代器處於 DETACHED 模式,且lastRet >= 0
                    //說明迭代器是正常結束,指的是遍歷完所有數據,
                    //即cursor=putIndex所引起的迭代器終止。
                    //正常結束的迭代器最後會給 lastItem 賦予 lastRet位置的值
                    // 當刪除該位置的元素時就需要將 lastItem 置空
                    else {
                        final E lastItem = this.lastItem;
                        // assert lastItem != null; lastItem一定不爲空
                        this.lastItem = null;
                        if (itemAt(lastRet) == lastItem)
                            removeAt(lastRet);
                    }
                // 不應該在 lastRet爲NONE 時調用 remove
                } else if (lastRet == NONE)
                    throw new IllegalStateException();
                    
                // 若是lastRet == REMOVED,則代表該位置數據已過時不用刪除
                
                // else lastRet == REMOVED and the last returned element was
                // previously asynchronously removed via an operation other
                // than this.remove(), so nothing to do.

				// 上面一開始將lastRet置爲NONE
				//所以若lastRet < 0 && cursor < 0 && nextIndex < 0爲true
				// 終止迭代器
                if (cursor < 0 && nextIndex < 0)
                    detach();
            } finally {
                lock.unlock();
                // assert lastRet == NONE;
                // assert lastItem == null;
            }
        }

具體刪除操作在 removeAt 方法中,這裏指的是 ArrayBlockingQueue#removeAt ,有別於 Itrs#removedAtItr#removedAt 方法。
那麼該實現需要考慮到哪些問題?
Itr#remove 中一開始就調用 incorporateDequeues 對下標變量進行了修正,也就是說迭代過程中要刪除的下標位置 removeIndex 一定是在 takeIndex 之後的包括 takeIndex。接下來需要考慮的問題就是刪除 removeIndex 位置的元素後對迭代鏈裏其它迭代器的影響,這就分兩種情況:
1,removeIndex == takeIndex;
2,removeIndex 在 takeIndex 之後。

對於情況 1 ,只要將takeIndex向後移動一位,隨後根據情況處理,即elementDequeued方法,之後再介紹。
對於情況 2,刪除位置在 takeIndex 之後,將後面的元素往前挪,通知迭代鏈上所有迭代器。
爲什麼這樣分?
takeIndex 位置是特殊的,迭代器在 next 方法中都會調用修正函數incorporateDequeues,所以對 takeIndex 的修改不需要特意通知其它迭代器,我們需要考慮的就是刪除後數組爲空 或是 takeIndex爲0的情況,處理邏輯在 elementDequeued 中。
那麼情況 2 呢,它刪除的位置在 takeIndexputIndex 之間,這就需要我們遍歷每個迭代器然後分情況來處理,邏輯在 Itrs#removedAt 中。

    void removeAt(final int removeIndex) {
        // assert lock.getHoldCount() == 1; 持有鎖
        // assert items[removeIndex] != null; 非空
        // assert removeIndex >= 0 && removeIndex < items.length; 未越界
        final Object[] items = this.items;
        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;
            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--;
            if (itrs != null)
                itrs.removedAt(removeIndex);
        }
        notFull.signal();
    }

注意該方法是被鎖保護的。

接下來看看 情況1 與 情況 2 在刪除元素後的處理操作,也就是 elementDequeueditrs#removedAt 方法。

4.1,Itrs#elementDequeued

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

當將 takeIndex 位置元素刪除後,需要對此時數組狀態進行判斷,分兩種情況:1,數組爲空;2,takeIndex 等於 0。

4.1.1,數組爲空 Itrs#queueIsEmpty

當數組爲空時終止所有迭代器。

        void queueIsEmpty() {
            // assert lock.getHoldCount() == 1;持有鎖
            for (Node p = head; p != null; p = p.next) {
                Itr it = p.get();
                if (it != null) {
                	//清除引用對象所引用的原對象,
                	//這樣通過get()方法就不能再訪問到原對象
                    p.clear();
                    //終止迭代器。
                    it.shutdown();
                }
            }
            head = null;
            itrs = null;
        }
4.1.1.1,終止迭代器操作 Itr#shutdown

對下標變量進行修改,最主要是將 prevTakeIndex 賦爲 DETACHED,適當時清掃函數 doSomeSweeping 會刪除該節點。

        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.
        }

正如註釋所說,nextItem可能會在 next 中返回,所以不要置爲 null。

4.1.2,takeIndex爲0 Itrs#takeIndexWrapped

該方法幹了兩件事:cycles 值加一 及 遍歷整條迭代器鏈刪除失效節點。
takeIndex 每此爲0都代表輪循了一輪, Itrscycles 變量記錄輪循次數。

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

關於判定迭代器失效條件:it == null || it.takeIndexWrapped()

  • it == null : 說明節點持有的迭代器對象被回收。
  • it.takeIndexWrapped :返回true,代表迭代器失效。
        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;
        }

迭代器是否失效的判斷:1,isDetached 返回true,說明迭代器處於 DETACHED 模式,等待被刪除。2,itrs.cycles - prevCycles > 1說明數據已過時,因爲每此next 操作都會調用修正函數 incorporateDequeues,他會修正迭代對象裏的 prevCycles,上面判斷爲true也就說明此時距迭代器上一次next操作至少過了兩輪,所以迭代器接下來要遍歷的數據都是過時數據,這裏直接調用 shutdown 終止迭代器。

總結一下 :

elementDequeued 的邏輯:數組爲空則終止所有迭代器;takeIndex 爲 0 將 記錄輪循次數的變量 cycles 的值加一,順帶遍歷整個迭代器鏈刪除無效節點,這裏無效的判斷條件爲
itr == null && isDetached && itrs.cycles - prevCycles > 1,歸納以下就是 被回收DETACHED模式等待被刪除迭代器接下來要返回的數據全部過時

4.2,Itrs#removedAt

刪除元素位置在 takeIndexputIndex 之間會對其它迭代器有什麼影響?Itrs#removedAt 在調用之前 removedIndex 位置的元素就已經被刪除,數組中其後直到 putIndex 位置的元素都往前移動一位,既然數組中元素位置變了自然需要對所有迭代器的 cursornextIndexlastRet 下標變量進行修正,如何修正?直接往前移動一位?不行,得分情況。

Itrs#removedAt 方法主要是遍歷整個迭代器鏈,查找失效節點刪除。對迭代器的修正操作實現在 Itr#removedAt中。

        void removedAt(int removedIndex) {
            for (Node o = null, p = head; p != null;) {
                final Itr it = p.get();
                final Node next = p.next;
                if (it == null || it.removedAt(removedIndex)) {
                    // 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;
        }

迭代器三個下標變量的修正分三種情況考慮:
1,removedIndex 在其之前,將下標變量減一。

2,removedIndex 與其等於,lastRetnextIndex 置爲 REMOVEDcursor 一般不做處理除非其等於 putIndex 將其置爲 NONE

爲什麼?這是由於它們的定義,lastRet 代表上一次迭代返回元素的下標,迭代器要使用到它來得到 lastItem,若該位置被刪除,需要將將其標識爲 REMOVEDnextIndex 同理;而 cursor 本身意味着下次返回的元素位置,該位置被刪除後會被後面的元素補上,這並不影響 cursor 定義,所以一般不做處理,只有在其等於 putIndex 時將其置爲 NONE,代表迭代的結束。

3,removedIndex 在其後,不需要做任何操作,因爲並未影響到當前迭代器。

那麼如何判斷 removedIndex 與下標變量的相對位置?
首先計算出刪除位置 removedIndexprevTakeIndex 的長度,該計算過程應該考慮到 cycles ,它代表輪循次數,迭代器存儲的輪循次數 prevCycles 可能已經過時,也就是此時 takeIndex 可能距迭代器上次操作之後輪循了多次,若是這樣那麼迭代器要迭代的數據就是過時數據。之後計算各個下標變量距 prevTakeIndex 的長度,二者相比來判斷相對位置。

上面說若位置在後不用做任何處理,經過上面的分析可以看出 在後 分爲兩種:1,輪循次數相等也就是當前迭代器迭代到此處,這種情況不用考慮,因爲並未對迭代器產生影響。2,輪循次數不等說明數據過時,這種情況也不處理,因爲當它們獲取鎖執行後自會在 incorporateDequeues 中對下標進行修正。

        boolean removedAt(int removedIndex) {
            // assert lock.getHoldCount() == 1;持有鎖
            if (isDetached()) // 迭代器處於 DETACHED 模式,返回true刪除該節點
                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++;
            // 刪除位置距本迭代器的遍歷開始位置prevTakeIndex的長度
            // 計算的過程應該將 輪循 考慮進來,cycleDiff 代表輪循的差值
            final int removedDistance =
                (cycleDiff * len) + (removedIndex - prevTakeIndex);
            // assert removedDistance >= 0;removedDistance 一定大於0
            
			// 接下來對cursor,lastRet,nextIndex 進行修正
            int cursor = this.cursor;
            if (cursor >= 0) {
                int x = distance(cursor, prevTakeIndex, len);
                // 刪除的位置就是該迭代器cursor指向的位置,一般不做處理
                if (x == removedDistance) {
                	//只有當其等於putIndex時置爲NONE,代表迭代的結束
                	//cursor置爲NONE後會導致迭代器detach
                    if (cursor == putIndex)
                        this.cursor = cursor = NONE;
                }
                // 刪除位置在其前,將cursor減一
                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);
            }
            // 爲true終止迭代器
            else if (cursor < 0 && nextIndex < 0 && lastRet < 0) {
                this.prevTakeIndex = DETACHED; // DETACHED模式
                return true;
            }
            return false;
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章