我知道修改ArrayList
会使它不是线程安全的。
➠ 但是,如果ArrayList
没有被修改,也许受到对Collections.unmodifiableList
的调用的保护,那么调用ArrayList::get
线程安全吗?
例如,是否可以将ArrayList
传递给 Java Stream 以并行处理其元素?
,如果不修改
ArrayList
,调用ArrayList::get
线程安全吗?
不,它不是线程安全的。
如果您执行以下操作,则会出现问题:
- 线程 A 创建并填充列表。
- 线程 A 将列表的引用传递给线程 B(在关系之前没有发生)
- 线程 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
时会遇到的情况。