接着上一篇,分析的是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;
}