是否可以仅同步java流中的终端方法调用?

时间:2016-11-09 10:54:37

标签: java multithreading java-stream

让我们考虑可以从差异线程访问的集合。我正在寻找一种使用java流来操纵集合的线程安全方法。

由于所有中间流操作都是惰性的,因此只能在终端方法中执行实际作业。我只能同步终端方法调用。

那么,代码是否是线程安全的:

public void print(Collection<String> list){
      Stream stream = list.stream();
      synchronized(this){
          stream.forEach(p -> System.out.println(p)); 
      }
}

1 个答案:

答案 0 :(得分:1)

当然,取决于您的应用程序,synchronized(this)是否足够(或根本不需要),以阻止在Stream遍历期间对源Collection的操作。

关于懒惰,确切的行为取决于源集合。考虑the contract of Collection.spliterator()

  

为了保留stream()parallelStream()方法的预期懒惰行为,分词符应该具有IMMUTABLECONCURRENT的特征,或者晚结合性。如果这些都不切实际,那么最重要的类应该描述分裂器记录的绑定和结构干扰策略,并应覆盖stream()parallelStream()方法以使用供应商创建流。分裂器,如:

Stream<E> s = StreamSupport.stream(() -> spliterator(), spliteratorCharacteristics)
     

这些要求确保stream()parallelStream()方法生成的流将在启动终端流操作时反映集合的内容。

为了缩短这一点,上面列出的所有变体都意味着在开始终端操作之前允许对源Collection进行修改(当然,排除IMMUTABLE分裂器)并且将由Stream操作反映出来。

所以如果你的应用程序以防止在执行终端操作时对源列表进行操作的方式保护对源列表的所有操作,那么你就是安全的。但请注意,接收列表为参数但在this上同步的方法看起来并不像正确保护列表。只是为了说清楚:所有线程必须在同一个对象上同步 以保护特定的共享资源。

但是抛开这一点,假设操作list的所有线程都将在listLock上正确同步,以下代码将起作用:

Stream stream = list.stream()
    .intermediateOp1(…)
    .intermediateOp2(…);

synchronized(listLock){
    stream.forEach(p -> System.out.println(p)); 
}

但这毫无意义。正如您所说的那样,为什么这样做的原因是将流中间操作链接到流不会导致任何实际处理。这是一个非常便宜的东西,所以从保护代码部分中排除它只意味着时间,即锁定,可能会相差几纳秒。

你几乎不会注意到与

的区别
synchronized(listLock) {
    list.stream()
        .intermediateOp1(…)
        .intermediateOp2(…)
        .forEach(p -> System.out.println(p)); 
}

因为大部分时间都花费在forEach之内。