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

Iterator type in java (weakly consistent)

我了解快速失败 (LinkedList) 和失败安全 (copyonwrite) 迭代器,但是弱一致性仍然是个谜。

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

我假设如果线程 A 创建了一个迭代器并中途执行,当线程 B 将一个项目放入数组开头的桶时,线程 A 的迭代器将看不到此更改。

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

是否可能出现nosuchelement异常?

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

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

  • they may proceed concurrently with other operations
  • they will never throw ConcurrentModificationException
  • they are guaranteed to traverse elements as they existed upon construction exactly once, and may (but are not guaranteed to) reflect any modifications subsequent to construction.

这并没有说明什么(以及使用 "consistent" 这个词可能隐含的意思)是迭代不会导致错误,例如 IndexOutOfBoundsExceptionNoSuchElementException.它也没有说明迭代是否会终止! (我相信它会。)从某种意义上说,这种行为确实是一致的,尽管保证非常薄弱。如果在迭代期间发生修改,第三个项目符号特别明确地不对迭代看到哪些元素做出任何保证。

考虑以下示例:

    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();
        }
    }

A ConcurrentLinkedDeqeue 是具有弱一致性迭代语义的集合的示例。此代码对其进行迭代并将看到的每个元素添加到副本中,但在迭代中间,双端队列被修改。

如果您使用 LinkedList 尝试此操作,您将获得预期的 ConcurrentModificationException。使用 ConcurrentLinkedDeque,输出列表为

    [a, b, c, d]

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

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

然而,它比我所说的 不一致 行为更强大。考虑以下使用索引(而不是 Iterator 对象)迭代 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.

因此您可以看到,在修改集合时对其进行迭代可能会出现很多问题。弱一致性迭代提供了针对重复元素以及可能发生的各种错误或无限循环的保证。 "weakness" 是它们几乎无法保证在迭代过程中准确观察到哪些元素。

最后,请注意 fail-fastweakly consistent 是在 Java 中定义和使用的特殊术语SE 规范。术语 "fail-safe" 未在官方 Java 文档中的任何地方使用。因此,我建议不要使用 "fail-safe" 来描述任何 Java 集合的并发修改策略。有些人认为 "fail-safe" 与 "fail-fast" 相反,您会在 Internet 上的各种博客和文章中看到这种情况。坦率地说,我认为这是应该避免的草率写作。