private static class Element
{
private final int value;
public Element(int value) { this.value = value; }
public int getValue() { return value; }
}
private static volatile int counter = 0;
public static void main(String[] args)
{
List<Element> list = new ArrayList<>();
for (int i = 0; i < 100 * 1000; i++)
{
list.add(new Element(1));
}
list.parallelStream().forEach(e -> counter += e.getValue());
System.out.println(counter);
}
大概,作者的意思是lambda表达式不应该接触变量,而是将一个值作为输入,在输出处赋值。例如:
很糟糕:
好的:
这意味着你应该在 lambda 表达式中使用外部(相对于表达式)不可变的值,而不是值和内部状态可以改变的外部变量。外部不可变值分别意味着有效的最终局部变量和原始类型的字段,以及有效的内部状态不会改变的最终对象。
这是因为 Streams 和 lambda 表达式被设计为多线程的。
使用变量 (
counter) 而不是值的问题在此示例中可见:不必指望值会显示在屏幕上
100000,因为存在竞争条件。在我的测试中,这段代码在 100,000 次中只能得到 299 次正确值。这就是为什么在 lambda 表达式中使用的局部变量必须是有效最终变量的原因之一。代码有效性
会导致局部变量的竞争条件,这将是Java多线程编程的新一轮问题。局部变量被认为是线程安全的,Java 开发人员不想打破这一原则。
您可以像这样欺骗编译器来限制有效的最终值:
因此,在使用有效的 final 局部变量时,仍有可能“搬起石头砸自己的脚”。当然,您不应该对该值再次计算错误感到惊讶。实际上,这绝对不是要走的路。
是的,你可以在这里使用
AtomicInteger:然而,这扼杀了代码并行化的整个思想。
在这种情况下,假定使用一堆
mapandreduce:map有和的部分reduce也可以这样写:可以在此处阅读 Brian Goetz(《Java 并发实践》的作者)关于此的文章。
然而,捕获变量而不是值的问题不仅仅发生在并行执行中。例如:
在此代码中,捕获了一个变量(不是有效的最终字段)
x,因此,而不是预期的输出将被显示
捕获相同值时:
不会有这样的问题/错误。