JUC源码解析-阻塞队列-迭代器(二)

27 篇文章 4 订阅
13 篇文章 1 订阅

接着上一篇,分析的是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;
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值