当前位置: 首页 > 知识库问答 >
问题:

Java 中的迭代器类型(弱一致性)

丌官炎彬
2023-03-14

我了解快速故障(LinkedList)和故障安全(复制写入)迭代器,但是弱一致性仍然是一个谜。

文档说它可能反映了基础集合的变化,但不能保证。所以我假设弱一致性不会创建支持集合的副本。(在并发Map中,它在同一个存储桶数组上工作)。

我假设如果线程A创建了一个迭代器并完成了一半,当线程B将一个项目放入数组开头的存储桶时,这个更改对线程A的迭代器不可见。

如果 B 将该项放在数组的末尾,则 A 会看到它。

是否可能有nosuchelement异常?

如果线程A创建了一个迭代器,然后遍历到具有下一个项目Y的项目X,然后jvm停止线程A并恢复线程B,后者删除了Y。这对线程A可见吗(我想是这样,否则并发映射将不是线程安全的,但对其迭代器是如何实现的一无所知),因为它对线程A不可见,所以它很容易引发异常。

共有1个答案

孔华池
2023-03-14

弱一致性的定义在 java.util.concurrent 包文档中给出。为方便起见,我将引用相关部分。关于弱一致性迭代器和拆分器,文档说:

  • 它们可以与其他操作同时进行
  • 他们永远不会抛出并发修改例外
  • 它们保证在建造时只穿越一次存在的元素,并且可能(但不能保证)反映施工后的任何修改。

这并没有说明(使用“一致”一词可能隐含的意思)迭代不会导致诸如< code > IndexOutOfBoundsException 或< code > NoSuchElementException 之类的错误。它也没有说迭代是否会终止!(我相信会的。)从某种意义上来说,这种行为确实是一致的,尽管这种保证相当薄弱。特别是第三点,如果在迭代过程中发生了修改,它并不保证迭代会看到什么元素。

考虑以下示例:

    List<String> input = Arrays.asList("a", "b", "c", "d", "e");
    List<String> output = new ArrayList<>();

    Deque<String> deque = new ConcurrentLinkedDeque<>(input);
    for (String s : deque) {
        output.add(s);
        if (s.equals("c")) {
            deque.addFirst("XXX");
            deque.removeLast();
        }
    }

ConcurrentLinkedDeqeue是具有弱一致迭代语义的集合的示例。这段代码对其进行迭代,并将看到的每个元素添加到副本中,但在迭代过程中,修改了deque。

如果您尝试使用链接列表执行此操作,您将获得预期的并发修改例外。使用并发链接的Deque,输出列表为

    [a, b, c, d]

请注意,“XXX”是在“e”被删除之前添加的,因此输出列表仅反映了在迭代期间对输入所做的一些修改。由于在这种情况下迭代是从左到右进行的,因此看不到对当前迭代点左侧所做的修改,而看到对当前迭代点右侧所做的修改也就不足为奇了。当然,并非所有集合都有如此简单的迭代顺序。

另请注意,输出不会反映输入在任何时间点发生的快照。(如果您想要快照语义学,您需要使用CopyOnWriteArrayList之类的东西。)唯一的保证是迭代看到的元素在某个时候出现在输入中。这是一个非常薄弱的保证!

然而,它比我所说的不一致行为更强大。请考虑以下代码,它使用索引(而不是迭代器对象)来循环访问 ArrayList:

    List<String> input = Arrays.asList("a", "b", "c", "d", "e");
    List<String> output = new ArrayList<>();

    List<String> arrayList = new ArrayList<>(input);
    for (int i = 0; i < arrayList.size(); i++) {
        String s = arrayList.get(i);
        output.add(s);
        if (i == 2) {                   // <<< MODIFY
            arrayList.add(0, "XXX");
        }
    }

在这种情况下,输出是

    [a, b, c, c, d, e]

它重复元素“c”,该元素在输入中只出现一次。这显然是一个错误。或者,假设标记为MODIFY的行更改为:

        if (s.equals("c")) {

在这种情况下,循环永远不会终止!您还可以很容易地想象这样的情况:使用索引样式循环,在正确(错误)的时间修改列表将导致IndexOutOfBoundsException

所以你可以看到,当一个集合被修改的时候,在这个集合上迭代会出现很多问题。弱一致性迭代提供了针对重复元素和可能出现的各种错误或无限循环的保证。“缺点”是它们几乎不能保证在迭代过程中观察到哪些元素。

最后,请注意,快速故障和弱一致性是在 Java SE 规范中定义和使用的特定术语。术语“故障安全”在官方Java文档中的任何地方都没有使用。因此,我建议不要使用“故障安全”来描述任何 Java 集合的并发修改策略。有些人认为“故障安全”与“快速故障”相反,您会在互联网上的各种博客和文章中看到这种情况。坦率地说,我认为这是应该避免的草率写作。

 类似资料:
  • 问题内容: 我了解故障快速(LinkedList)和故障安全(copyonwrite)迭代器,但是弱一致性仍然是个谜。 文档说它可能反映了基础馆藏的变化,但不能保证。因此,我认为弱一致性不会创建后备集合的副本。(在并发Map中,它可在同一bucketarray上运行)。 我假设线程A创建了一个迭代器并进行了一半,当线程B将一个项目放到数组开头的存储桶中时,线程A的迭代器将看不到该更改。 如果B将该

  • 问题内容: 失败安全迭代器是那些不会失败的迭代器 。 但是和之间有什么区别? 都一样吗 问题答案: 无论和迭代器不乱扔。 依赖CAS( compare-and-swap )的集合具有弱一致性的迭代器,该迭代器反映了自创建以来对其后备集合进行的部分更改,但不一定反映所有更改。例如,如果集合中的元素在迭代器到达之前已被修改或删除,则它肯定会反映出这些更改,但对插入没有任何保证。 迭代器机制复制内部Co

  • JavaScript中有没有已知的技巧来区分和之间的区别,而不触发迭代? 我正在尝试实现以下类型检查器: 我知道调用会告诉我们这一点,但在无法触发迭代时我需要它。 此外,即使我在TypeScript中给出了示例,我也需要在运行时严格检查它。

  • 问题内容: 什么是迭代器和集合?这两个有关系吗? 接口迭代器是否只预定义了这些方法名称,还是用户定义了这些方法名称?下面的这四行实际上说明了什么? 谢谢。我正在看一本藏书。 问题答案: 顾名思义,Java集合是事物的集合。如果您不知道该单词,请在字典中查找。 有很多类型的集合。以集合的数学概念为例。您可以将任意事物放入集合中,但是永远不会包含同一事物。在集中的东西是没有顺序的,那就是你不能说 A

  • 我试图为自己的泛型类编写自己的迭代器。我一直在看几个YouTube教程,在网上搜索。 IntelliJ指出的问题是,我不能像我试图的那样在迭代器类中使用getleft和getright,因为非静态方法不能从静态上下文中引用。我一直在研究静态和更多,但无法解决这个问题。我是完全走错了路,还是至少我的方法有点接近? 运行时: 我遇到了一个无休止的循环,打印5。因此,迭代器本身可以工作,但我的方法有一个

  • 我想创建一个自定义的树数据结构,只有节点,我可以迭代他们。然后,我可以扩展这个类,并有非常基本的树 我已经开始工作了,但是当我试图扩展Node类时,问题就来了。对于扩展类,继承的迭代器方法仍然返回节点迭代器,这意味着我每次都要强制转换。下面是我遇到的问题的一个基本示例。让我们建立一个包含整数的树: 是否有一种简单的方法可以解决这个问题,而不需要将iterator()方法从Node类复制到Integ