如何同步这个lambda语句?



Java/JDK如何同步这个lambda语句?

package sybex.ch00.exercies;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Q03 {
public static void main(String[] args) {
List<Integer> data = new ArrayList<>();
IntStream.range(0, 100).parallel().forEach(s -> data.add(s));
System.out.println(data.size());
}
}

我读了书,他们说在synchronized之后lamba将使线程安全,并返回100,但我不知道该怎么做。请给我指路。

Stream的角度来看,外部同步是错误的这样做(被迫不良使用的副作用)。文档中对副作用的说明如下:

流操作的行为参数的副作用通常是不被鼓励的,因为它们通常会导致无意中违反无状态要求,以及其他线程安全危害。

也:

许多可能会产生副作用的计算可以在没有副作用的情况下更安全有效地表达,例如使用约简而不是可变累加器。然而,使用println()进行调试等副作用通常是无害的。少数流操作,如forEach()和peek(),只能通过副作用进行操作;这些应该小心使用。

另外,外部同步会影响性能:

如果并行执行,ArrayList的非线程安全性将导致不正确的结果,并且添加所需的同步将导致争用,从而破坏并行性的好处。此外,这里使用副作用是完全没有必要的;forEach()可以简单地替换为一个更安全、更有效、更适合并行化的约简操作

正确的方法(对于Streams)是使用收集器。

List<Integer> data = IntStream.range(0, 100)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println(data.size());

没有特别的技巧,只需使用synchronized块:

public class Q03 {
public static void main(String[] args) {
List<Integer> data = new ArrayList<>();
IntStream.range(0, 100).parallel().forEach(s -> {
synchronized(data) { data.add(s); }
});
System.out.println(data.size());
}
}

根据你运行的上下文,你必须选择要同步的对象。这里data是一个不错的选择,或者您可以创建一个对象来锁定:

public class Q03 {
public static void main(String[] args) {
List<Integer> data = new ArrayList<>();
Object lock = new Object();
IntStream.range(0, 100).parallel().forEach(s -> {
synchronized(lock) { data.add(s); }
});
System.out.println(data.size());
}
}

我会使用Collections.synchronizedList:

public static void main(String[] args) {
List<Integer> data = Collections.synchronizedList(new ArrayList<>());
IntStream.range(0, 100).parallel().forEach(s -> data.add(s));
System.out.println(data.size());
}

在使用该列表的迭代器时要小心,请参阅链接的JavaDocs。

您也可以在java.util.concurrent中使用一个并发集合,但是对于您所展示的情况,CopyOnWriteArrayList可能会有明显的性能问题。另一方面,我没有检查上述解决方案的性能。

最新更新