Java8流中副作用的危险是什么?
我正在努力理解我在Streams文档中找到的警告。我养成了使用forEach()作为通用迭代器的习惯。这让我开始写这类代码:Java8流中副作用的危险是什么?,java,java-stream,Java,Java Stream,我正在努力理解我在Streams文档中找到的警告。我养成了使用forEach()作为通用迭代器的习惯。这让我开始写这类代码: public class FooCache { private static Map<Integer, Integer> sortOrderCache = new ConcurrentHashMap<>(); private static Map<Integer, String> codeNameCache = new
public class FooCache {
private static Map<Integer, Integer> sortOrderCache = new ConcurrentHashMap<>();
private static Map<Integer, String> codeNameCache = new ConcurrentHashMap<>();
public static void populateCache() {
List<Foo> myThings = getThings();
myThings.forEach(thing -> {
sortOrderCache.put(thing.getId(), thing.getSortOrder());
codeNameCache.put(thing.getId(), thing.getCodeName())
});
}
}
公共类缓存{
私有静态映射sortOrderCache=new ConcurrentHashMap();
私有静态映射codeNameCache=新ConcurrentHashMap();
公共静态void populateCache(){
List myThings=getThings();
神话。forEach(事物->{
put(thing.getId(),thing.getSortOrder());
codeNameCache.put(thing.getId(),thing.getCodeName())
});
}
}
这是一个微不足道的例子。我知道该代码违反了Oracle关于有状态LAMDA和副作用的警告。但我不明白为什么会有这样的警告
运行此代码时,它的行为似乎与预期一致。那么,我如何打破这一点来证明这是一个坏主意
在排序中,我读到:
如果并行执行,ArrayList的非线程安全性将降低
导致不正确的结果,添加所需的同步将导致
竞争,破坏了并行性的好处
但是有谁能澄清一下,帮助我理解这个警告吗?副作用经常会对状态和上下文做出假设。并行运行时,您不能保证看到元素的特定顺序,并且多个线程可能同时运行
除非您为此编写代码,否则这可能会产生非常微妙的bug,在尝试并行时很难跟踪和修复这些bug 我相信文档中提到了以下代码演示的副作用:
List<Integer> matched = new ArrayList<>();
List<Integer> elements = new ArrayList<>();
for(int i=0 ; i< 10000 ; i++) {
elements.add(i);
}
elements.parallelStream()
.forEach(e -> {
if(e >= 100) {
matched.add(e);
}
});
System.out.println(matched.size());
从Javadoc:
还要注意,试图从行为层访问可变状态
参数在安全性和可靠性方面给您带来了一个糟糕的选择
表现如果不同步对该状态的访问,则
数据竞争,因此您的代码被破坏,但如果
同步对该状态的访问,可能会导致冲突
您希望从中受益的并行性。最好的方法是
避免使用有状态的行为参数来完全流化操作;
通常有一种方法可以重组流管道以避免
庄严
这里的问题是,如果你访问一个可变状态,你会失去两个方面:
- 安全性,因为您需要
流尝试最小化的同步
- 性能,因为所需的同步会降低您的成本(在您的示例中,如果您使用
,则会降低成本)ConcurrentHashMap
- 如果要使用
和多线程流,则需要使用流
,如parralelStream()
;目前,由myThings.parralelStream()
提供的java.util.Collection
方法对于每个forEach
都非常简单
- 您使用
作为HashMap
不是线程安全的;您需要使用静态成员,并对其进行变异<代码>哈希映射
ConcurrentHashMap
流
,则不得改变流的源:
myThings.stream().forEach(thing -> myThings.remove(thing));
这可能会起作用(但我怀疑它会抛出一个ConcurrentModificationException
),但这可能不会起作用:
myThings.parallelStream().forEach(thing -> myThings.remove(thing));
这是因为ArrayList
不是线程安全的
如果使用同步视图(Collections.synchronizedList
),则由于每次访问时都进行了同步,因此会对其产生性能影响
在您的示例中,您更愿意使用:
sortOrderCache = myThings.stream()
.collect(Collectors.groupingBy(
Thing::getId, Thing::getSortOrder);
codeNameCache= myThings.stream()
.collect(Collectors.groupingBy(
Thing::getId, Thing::getCodeName);
finisher(这里的groupingBy
)执行您正在执行的工作,可能会被顺序调用(我的意思是,流可能会在多个线程中被拆分,finisher可能会被调用多次(在不同的线程中),然后可能需要合并
顺便说一下,您可能最终会删除
codeNameCache
/sortOrderCache
,只需存储id->Thing映射即可。您可以提供警告吗?您的示例中没有流。有一个名为forEach()
的终端流操作,但您甚至没有流。您正在调用List.forEach()
。我不知道你所说的“别名”是什么意思,但不是。它是一个不相关的方法。事实上,它是由Iterable
接口声明的,该接口甚至没有流方法。
sortOrderCache = myThings.stream()
.collect(Collectors.groupingBy(
Thing::getId, Thing::getSortOrder);
codeNameCache= myThings.stream()
.collect(Collectors.groupingBy(
Thing::getId, Thing::getCodeName);