我知道出于并发原因,我无法在Java 8中更新lambda中局部变量的值。所以这是非法的:
double d = 0;
orders.forEach( (o) -> {
d+= o.getTotal();
});
但是,如何更新实例变量或更改本地对象的状态?例如,Swing应用程序我有一个按钮和一个声明为实例变量的标签,当我单击按钮时我想隐藏标签< / p>
jButton1.addActionListener(( e) -> {
jLabel.setVisible(false);
});
我没有编译器错误并且工作正常,但是......改变lambda中对象的状态是正确的吗?我将来会遇到并发问题还是坏事?
这是另一个例子。想象一下,以下代码位于servlet的方法doGet中 我会在这里遇到一些问题吗?如果答案是肯定的:为什么?
String key = request.getParameter("key");
Map<String, String> resultMap = new HashMap<>();
Map<String, String> map = new HashMap<>();
//Load map
map.forEach((k, v) -> {
if (k.equals(key)) {
resultMap.put(k, v);
}
});
response.getWriter().print(resultMap);
我想知道的是:什么时候改变lambda中对象实例的状态是正确的?
答案 0 :(得分:14)
您的假设不正确。
你只能在lambdas中更改有效的最终变量,因为lambdas是匿名内部类的语法糖*。 *它们实际上不仅仅是语法糖,但这与此无关。
在匿名内部类中,您只能更改有效的最终变量,因此对于lambdas也是如此。
只要编译器允许,你就可以用lambdas做任何你想做的事情,现在进入行为部分:
一些例子:
class MutableNonSafeInt {
private int i = 0;
public void increase() {
i++;
}
public int get() {
return i;
}
}
MutableNonSafeInt integer = new MutableNonSafeInt();
IntStream.range(0, 1000000)
.forEach(i -> integer.increase());
System.out.println(integer.get());
无论发生什么情况,都会按预期打印1000000,即使它取决于之前的状态。
现在让我们并行化流:
MutableNonSafeInt integer = new MutableNonSafeInt();
IntStream.range(0, 1000000)
.parallel()
.forEach(i -> integer.increase());
System.out.println(integer.get());
现在它打印不同的整数,如199205或249165,因为其他线程并不总是看到不同线程所做的更改,因为没有同步。
但是说我们现在摆脱了我们的虚拟类并使用AtomicInteger
,是线程安全的,我们得到以下内容:
AtomicInteger integer = new AtomicInteger(0);
IntStream.range(0, 1000000)
.parallel()
.forEach(i -> integer.getAndIncrement());
System.out.println(integer.get());
现在它再次正确打印1000000 然而,同步成本很高,而且我们几乎失去了并行化的所有好处。
答案 1 :(得分:5)
一般情况下:是的,您可能会遇到并发问题,但只会遇到已经存在的并发问题。 Lambdafying它不会使代码非线程安全在以前,或反之亦然。在您给出的示例中,您的代码(可能)是线程安全的,因为只在事件派发线程上调用ActionListener
。如果您已观察到Swing单线程规则,则没有其他线程访问jLabel
,如果是,则不会对其进行线程干扰。但这个问题与使用lambdas是正交的。
答案 2 :(得分:2)
如果“forEach”分发到不同的线程/核心,则可能会出现并发问题。考虑使用原子或并发结构(如ConcurrentHashMap)