我试图理解在“流文档”中发现的警告。我已经习惯了使用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())
});
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个简单的例子。我了解该代码违反了Oracle关于有状态lamda和副作用的警告。但是我不明白为什么这个警告存在。
运行此代码时,它似乎表现出预期的效果。那么,我如何打破这一点来说明为什么这是一个坏主意?
在某种程度上,我读到以下内容:
如果并行执行,则ArrayList的非线程安全性将导致错误的结果,而添加所需的同步将导致争用,从而削弱了并行性的优势。
但是,谁能增加清晰度以帮助我理解警告?
来自 Javadoc:
另请注意,尝试从行为参数访问可变状态会给您带来安全性和性能方面的错误选择;如果您不同步对该状态的访问,则会出现数据争用,因此您的代码会被破坏,但如果您同步对该状态的访问,则可能会出现争用破坏您正在寻求从中受益的并行性的风险。最好的方法是完全避免有状态的行为参数来流操作;通常有一种方法可以重组流管道以避免有状态。
这里的问题是,如果您访问可变状态,则会在两侧松动:
Stream会尽量减少同步ConcurrentHashMap,则会产生成本)。现在,在您的示例中,这里有几点:
Stream多线程流,你需要使用parralelStream()如下myThings.parralelStream();就目前情况而言,forEach提供的方法java.util.Collection很简单for each。HashMap作为static成员使用并改变它。HashMap不是线程安全的;你需要使用一个ConcurrentHashMap.在 lambda 中,以及 a 的情况下Stream,您不得改变流的源:
myThings.stream().forEach(thing -> myThings.remove(thing));
Run Code Online (Sandbox Code Playgroud)
这可能有效(但我怀疑它会抛出一个ConcurrentModificationException),但这可能不起作用:
myThings.parallelStream().forEach(thing -> myThings.remove(thing));
Run Code Online (Sandbox Code Playgroud)
那是因为ArrayList不是线程安全的。
如果您使用同步视图(Collections.synchronizedList ),那么您将获得性能,因为您在每次访问时都进行同步。
在您的示例中,您宁愿使用:
sortOrderCache = myThings.stream()
.collect(Collectors.groupingBy(
Thing::getId, Thing::getSortOrder);
codeNameCache= myThings.stream()
.collect(Collectors.groupingBy(
Thing::getId, Thing::getCodeName);
Run Code Online (Sandbox Code Playgroud)
终结者(这里是groupingBy)完成您正在做的工作,并且可能会按顺序调用(我的意思是,流可能会被分割到多个线程中,完成器可能会被调用多次(在不同的线程中),然后可能需要合并。
顺便说一句,您最终可能会删除codeNameCache/sortOrderCache并简单地存储 id->Thing 映射。