让我们考虑可以从不同线程访问的集合。我正在寻找一种线程安全的方法来使用 java 流操作集合。
由于所有中间流操作都是惰性的,所以真正的作业只能在终端方法中执行。我只能同步终端方法调用。
那么,代码是否线程安全:
public void print(Collection<String> list){
Stream stream = list.stream();
synchronized(this){
stream.forEach(p -> System.out.println(p));
}
}
最佳答案
当然,这取决于您的应用程序 synchronized(this)
是否足以(或根本必要)来阻止在 Stream 遍历期间对源 Collection 进行操作。
关于惰性,确切的行为取决于源集合。考虑the contract of Collection.spliterator()
:
In order to preserve expected laziness behavior for the
stream()
andparallelStream()
methods, spliterators should either have the characteristic ofIMMUTABLE
orCONCURRENT
, or be late-binding. If none of these is practical, the overriding class should describe the spliterator's documented policy of binding and structural interference, and should override thestream()
andparallelStream()
methods to create streams using a Supplier of the spliterator, as in:Stream<E> s = StreamSupport.stream(() -> spliterator(), spliteratorCharacteristics)
These requirements ensure that streams produced by the
stream()
andparallelStream()
methods will reflect the contents of the collection as of initiation of the terminal stream operation.
为了缩短这一点,上面列出的所有变体都意味着允许在开始终端操作之前对源 Collection 进行修改(当然,不包括 IMMUTABLE
spliterators),并将由 Stream 操作反射(reflect).
因此,如果您的应用程序保护对源列表的所有操作,以防止在执行终端操作时对源列表进行操作,那么您就安全了。但请注意,接收列表作为参数,但在 this
上进行同步的方法看起来并没有正确保护列表。只是为了明确一点:所有线程必须在同一对象上同步以保护特定的共享资源。
但抛开这一点,假设操作 list
的所有线程都将在 listLock
上正确同步,则以下代码将起作用:
Stream stream = list.stream()
.intermediateOp1(…)
.intermediateOp2(…);
synchronized(listLock){
stream.forEach(p -> System.out.println(p));
}
但这毫无意义。正如您自己所说,这样做的原因是,将中间操作链接到流不会导致任何实际处理。这是一个非常便宜的东西,因此将其从 protected 代码部分中排除只意味着持有锁的时间可能会相差几纳秒。
你几乎不会注意到有什么不同
synchronized(listLock) {
list.stream()
.intermediateOp1(…)
.intermediateOp2(…)
.forEach(p -> System.out.println(p));
}
因为无论如何,大部分时间都花在 forEach
中。
关于java - 是否可以仅同步 java 流中的终端方法调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40505243/