'ArrayList::get' 是线程安全的吗?



我知道修改ArrayList会使它不是线程安全的。

➠ 但是,如果ArrayList没有被修改,也许受到对Collections.unmodifiableList的调用的保护,那么调用ArrayList::get线程安全吗?

例如,是否可以将ArrayList传递给 Java Stream 以并行处理其元素?

但是

,如果不修改ArrayList,调用ArrayList::get线程安全吗?

不,它不是线程安全的。

如果您执行以下操作,则会出现问题:

  1. 线程 A 创建并填充列表。
  2. 线程 A 将列表的引用传递给线程 B(在关系之前没有发生)
  3. 线程 B 调用列表中的get

除非在 1 和 3 之间的链之前发生适当的事件,否则线程 B 可能会看到过时的值......偶尔。。。在某些平台上,在某些工作负载下。

有一些方法可以解决这个问题。 例如,如果线程 A 在步骤 1 之后启动线程 B,则之前会发生 。 类似地,如果 A 通过正确同步的 setter/getter 调用或易失变量将列表引用传递给 B,则会发生。

但最重要的是,(仅仅)不更改列表不足以使其线程安全。

<小时 />

...也许受到Collections.unmodifiableList召唤的保护

Collections.unmodifiableList的创建应该提供关系之前发生的事情......前提是您通过包装器访问列表,而不是直接通过ArrayList::get.


例如,是否可以将ArrayList传递给 Java Stream 以对其元素进行并行处理?

这是一个特定的情况。 流机制将提供关系之前发生的事件。前提是按预期使用它们。这很复杂。

这来自Spliterator接口javadoc。

"尽管它们在并行算法中具有明显的实用性,但拆分器并不期望是线程安全的;相反,使用拆分器的并行算法的实现应确保拆分器一次仅由一个线程使用。这通常很容易通过串行线程约束来实现,这通常是通过递归分解工作的典型并行算法的自然结果。调用trySplit()的线程可能会将返回的Spliterator传递给另一个线程,而另一个线程又可以遍历或进一步拆分该Spliterator。如果两个或多个线程在同一拆分器上同时运行,则未定义拆分和遍历的行为。如果原始线程将拆分器交给另一个线程进行处理,则最好在tryAdvance()使用任何元素之前进行切换,因为某些保证(例如 SIZE 拆分器的estimateSize()精度)仅在遍历开始之前有效。

换句话说,线程安全是Spliterator实现和Stream实现的共同责任。

思考这个问题的简单方法是"奇迹发生"......因为如果没有,那么并行流将无法使用。

但请注意,Spliterator根本不需要使用ArrayList::get

线程安全只是一个问题,正如您所说的,当值可以在线程之间更改时。如果未添加或删除元素,则对象保持不变,并且所有线程都可以轻松对其进行操作。对于 Java 中的大多数对象来说,这是相同的。

您可以像这里看到的那样跨线程添加到 ArrayList,但我不会指望它。

不,ArrayList.get()本身并不是线程安全的,因为它不修改List。 您仍然需要一些东西来在每个get()和每个修改列表的方法调用之间创建发生前关系。

但是,假设您先实例化并填充列表,然后执行多个get(),从不再次修改它,或者至少直到所有get()之后的某个同步点之后才修改它。 这样,您就不需要在各个get()之间相互同步,并且您可以在get()和初始化阶段结束之间获得廉价的同步。 这实际上是您在作为并行流计算的输入提供的非共享List时会遇到的情况。

最新更新