Java8流中副作用的危险是什么?

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

我正在努力理解我在Streams文档中找到的警告。我养成了使用forEach()作为通用迭代器的习惯。这让我开始写这类代码:

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
在lambda中,如果是
,则不得改变流的源:

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);