接着上一篇,分析的是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#removedAt 和 Itr#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 呢,它刪除的位置在 takeIndex 到 putIndex 之間,這就需要我們遍歷每個迭代器然後分情況來處理,邏輯在 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 在刪除元素後的處理操作,也就是 elementDequeued
與 itrs#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都代表輪循了一輪, Itrs
的 cycles 變量記錄輪循次數。
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
刪除元素位置在 takeIndex 與 putIndex 之間會對其它迭代器有什麼影響?Itrs#removedAt 在調用之前 removedIndex 位置的元素就已經被刪除,數組中其後直到 putIndex 位置的元素都往前移動一位,既然數組中元素位置變了自然需要對所有迭代器的 cursor , nextIndex , lastRet 下標變量進行修正,如何修正?直接往前移動一位?不行,得分情況。
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 與其等於,lastRet 與 nextIndex 置爲 REMOVED,cursor 一般不做處理除非其等於 putIndex 將其置爲 NONE。
爲什麼?這是由於它們的定義,lastRet 代表上一次迭代返回元素的下標,迭代器要使用到它來得到 lastItem,若該位置被刪除,需要將將其標識爲 REMOVED;nextIndex 同理;而 cursor 本身意味着下次返回的元素位置,該位置被刪除後會被後面的元素補上,這並不影響 cursor 定義,所以一般不做處理,只有在其等於 putIndex 時將其置爲 NONE,代表迭代的結束。
3,removedIndex 在其後,不需要做任何操作,因爲並未影響到當前迭代器。
那麼如何判斷 removedIndex 與下標變量的相對位置?
首先計算出刪除位置 removedIndex 距 prevTakeIndex 的長度,該計算過程應該考慮到 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;
}