不同迭代器实现造成的遍历问题

文章对比了LinkedList和LinkedBlockingQueue在遍历过程中进行删除操作的行为差异。LinkedList的迭代器通过下标遍历,删除元素时可能出现并发同步异常,而LinkedBlockingQueue的迭代器则通过Node结构保存下一个节点,确保遍历安全。
摘要由CSDN通过智能技术生成

不同迭代器实现造成的遍历问题

一、缘起

今天在使用foreach循环遍历LinkedBlockingQueue的时候,发现一边遍历一遍删除的操作竟然没有出错!代码实现如下:

LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.add("a");
queue.add("b");
Iterator<String> iterator1 = queue.iterator();
while (iterator1.hasNext()) {
    String s2 = iterator1.next();
    System.out.println(s2);
    queue.remove(s2);
}
System.out.println(queue);

结果如下:

image-20230217104334999

按照正常集合(List)遍历删除是会出错的,具体如下:

LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    System.out.println(s);
    list.remove(s);
}
System.out.println(list);

结果如下:

image-20230217104157575

这奇奇怪怪的不同激起了我好奇心,于是有了这篇文章…

二、LinkedList迭代器实现

翻阅LinkedList中的iterator()方法,它返回了一个Iterator迭代器类,迭代器类中拥有next()方法返回下一个集合元素,具体实现如下:

public E next() {
    return itr.previous();
}

继续查看previous()方法

public E previous() {
    checkForComodification();
    if (!hasPrevious())
        throw new NoSuchElementException();

    lastReturned = next = (next == null) ? last : next.prev;
    nextIndex--;
    return lastReturned.item;
}

哦吼,华生,恭喜你发现了盲点!这个方法中含有一个nextIndex--,这不由得让我们联想到它的遍历方式是不是通过下标去遍历的,我们再来看看这个nextIndex--在迭代器类中的初始化代码:

ListItr(int index) {
    // assert isPositionIndex(index);
    next = (index == size) ? null : node(index);
    nextIndex = index;
}

看到这,发现nextIndex确实是保存了下一个节点的下标,当原始集合发生变化时,迭代器中的下标并没有及时改变,于是便出现了并发同步异常

三、LinkedBlockingQueue迭代器实现

接下来再来看看Queue的迭代器类中的next的实现:

public E next() {
    Node<E> p;
    if ((p = next) == null)
        throw new NoSuchElementException();
    lastRet = p;
    E x = nextItem;
    fullyLock();
    try {
        E e = null;
        for (p = p.next; p != null && (e = p.item) == null; )
            p = succ(p);
        next = p;
        nextItem = e;
    } finally {
        fullyUnlock();
    }
    return x;
}

发现它是通过Node类保存下一个节点位置的,Node类的实现如下:

static class Node<E> {
    E item;

    /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
    Node<E> next;

    Node(E x) { item = x; }
}

发现它确实是保存了下一个节点的位置的。

所以无论原始队列再怎么变,自己保存的下一个节点的位置不会改变,也就不会出现List那样的错误。

四、总结

通过数组实现的下标保存并不能保证同步,需要额外的加入同步逻辑,但是通过Node保存下一个节点位置的方法无需同步,更加安全便捷。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值