Iterable.forEach允许平行吗?

Is Iterable.forEach allowed to be parallel?

从5开始使用Java,今天在GitHub PR中遇到一段有趣的代码: 有人在 Java 8.

中使用 List#forEachAtomicInteger

现在我想知道这是否可能是必要的 - 瞧瞧,查阅 Java 8 Documentation(其中引入了 Iterable#forEach,以及相似的 stream().parallel().forEach,后者其中显然可能是平行的)我读到:

Unless otherwise specified by the implementing class, actions are performed in the order of iteration (if an iteration order is specified).

(强调我的)

... 因此,鉴于 Stream 强调 实施 Iterable(从 Java 18 开始)- 应该我真的不得不假设 Iterable 的未知实现者可能会并行执行 forEach

(如果预期会根据 Java 版本发生变化,我想要一个答案来处理从 8 开始的每个不同版本。)

请注意,短语“除非实施 class 另有规定”已从 recent versions of the specification 中删除:

Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception. Actions are performed in the order of iteration, if that order is specified. Exceptions thrown by the action are relayed to the caller.

由于 List 有定义的遭遇顺序,并且以定义的顺序执行操作会排除并发执行,我们可以明确地说,在您的问题中提到的特定情况下,并发执行被明确排除。

更广泛的问题 IterableforEach 是否排除了一般情况下指定操作的并发执行,当没有定义的遇到顺序时,只能通过求助于 Principle of least astonishment,结论是规范应该明确提及在某些情况下是否允许并发执行。据我所知,Java的整个API规范都遵循这个原则,所以没有理由认为它不适用于这个特定的方法。

最值得注意的是,每个人都知道 Stream API 允许并行处理,因为它已被显着记录。同样,需要 Arrays.sort(…) does not permit a surprising parallel evaluation of the Comparator, without mentioning it explicitely, but rather, an explicit use of Arrays.parallelSort(…) 才能启用它。

这同样适用于实际的并发集合。例如。当你在 ConcurrentHashMap 上调用 keySet().forEach(…) 时,它不会在有并发更新时失败,因为一般 weakly consistent iteration policy specifies, but also run sequentially on the caller’s thread, as every method does when not specified otherwise. You’d need a dedicated forEach method 用于并行处理。


所以使用AtomicInteger是不合理的。这是一种普遍存在的不良编程风格,假装是函数式编程,但仍然坚持循环思考,使用 forEach 并在函数外修改变量。由于这对于局部变量是不可能的,因此 AtomicInteger 在这里仅用作 int 堆变量的容器。使用单个元素 int[] 数组也可以,但仍然不推荐。相反

  1. 继续使用普通循环,或者

  2. 使用真正的函数式方法,例如

    int result = words.stream()
        .mapToInt(word -> {
            // count matches locally and return the int
        })
        .sum();
    

    long result = words.stream()
        .flatMap(word -> {
            // return a stream of matches
        })
        .count();