我读这大约Lambda表达式梦幻般的文章,以下是未清除的对我说:
不要指望编译器捕获所有并发访问错误.禁止变异只适用于局部变量.
我不确定自我实验会涵盖所有情况,所以我正在寻找一个明确定义的规则:
不要指望编译器捕获所有并发访问错误.禁止变异只适用于局部变量.如果matches是封闭类的实例或静态变量,则不会报告错误,即使结果与未定义相同.
更新1:
自由变量 - 即不是参数且未在代码中定义的变量.
简单来说,我可以得出结论,自由变量是不是Lambda表达式参数的所有变量,并且没有在同一个Lambda表达式中定义?
你的术语"自由变量"充其量是误导性的.如果你不是在谈论局部变量(必须有效地最终被捕获),那么你所说的是堆变量.
堆变量可以是实例字段,static字段或数组元素.为了从周围的上下文中无条件地访问实例变量,lambda表达式可以(并将)通过捕获的this引用访问它们.对于其他实例字段以及数组元素,无论如何都需要通过变量进行显式访问,因此很清楚,如何访问堆变量.只能static直接访问字段.
规则很简单,除非声明,否则final可以在lambda表达式的内部或外部修改所有规则.请记住,lambda表达式可以调用任意方法,无论如何都包含任意代码.这是否会导致问题,取决于您如何使用lambda表达式.您甚至可以创建不直接修改变量的函数的问题,而不需要任何并发性,例如
ArrayList<String> list=new ArrayList<>(Arrays.asList("foo", "bar"));
list.removeIf(s -> list.remove("bar"));
Run Code Online (Sandbox Code Playgroud)
java.util.ConcurrentModificationException由于正在进行的迭代中的列表修改,可能会抛出一个.
同样,即使您确保以线程安全的方式完成对变量本身的修改,修改并发上下文中的变量或资源也可能会破坏它.这都是关于您正在使用的API的合同.
最值得注意的是,当使用并行Streams时,您必须意识到函数不仅由不同的线程进行评估,它们还在评估Stream的任意元素,而不管它们的遭遇顺序如何.对于Stream处理的最终结果,实现将以重新建立遭遇顺序的方式组合部分结果,如果需要,但是中间操作以任意顺序评估元素,因此您的函数不仅必须是线程安全的,而且也不依赖于特定的处理订单.在某些情况下,他们甚至可能处理对最终结果没有贡献的元素.
由于你的子弹3指的是"在一个块结束之后",我想强调一下,在你的lambda表达式中的哪个位置发生修改(或可感知的副作用)是无关紧要的.
一般来说,你最好使用没有这种副作用的功能.但这并不意味着他们一般都被禁止.
对于更简单的主题,这看起来像复杂的"单词".规则与匿名类几乎相同.
例如,编译器捕获了这个:
int x = 3;
Runnable r = () -> {
x = 6; // Local variable x defined in an enclosing scope must be final or effectively final
};
Run Code Online (Sandbox Code Playgroud)
但同时这样做是完全合法的(从编译器的角度来看):
final int x[] = { 0 };
Runnable r = () -> {
x[0] = 6;
};
Run Code Online (Sandbox Code Playgroud)
您提供并使用的示例matches:
List<Path> matches = new ArrayList<>();
List<Path> files = List.of();
for (Path p : files) {
new Thread(() -> {
if (1 == 1) {
matches.add(p);
}
}).start();
}
Run Code Online (Sandbox Code Playgroud)
有同样的问题.编译器不会抱怨你编辑匹配(因为你没有改变引用matches- 所以它是effectively final); 但同时这可以undefined results.side-effects一般而言,此操作已经并且不鼓励.未定义的结果将来自于您显然matches不是一个thread-safe集合的事实.
你最后一点:Does the result of the mutation is undefined even when I use a synchroniziton algorithm?.当然不是.通过适当的同步更新,变量outsidelambda(或流)将起作用 - 但是不鼓励,主要是因为还有其他方法可以实现.
编辑
好的,所以自由变量是那些未在lambda代码本身内定义的变量,或者不是 lambda本身的参数.
在这种情况下,1)的答案是:lambda表达式对方法去糖,规则free-variables与匿名类相同.这已经多次讨论过了,就像这里一样.这实际上也回答了第二个问题 - 因为规则是相同的.显然任何final or effectively final可以变异的东西.对于原语 - 这意味着它们不能被变异; 对于对象,您不能改变引用(但可以更改基础数据 - 如我的示例所示).对于3) - 是的.