java - 为什么在 lambda 表达式中使用的变量应该是 final 或有效的 final

标签 java lambda concurrency

此问题之前已在 here 上提出过
我的问题为什么在 here 上得到了回答
但我对答案有些怀疑。 提供的答案提到-

Although other answers prove the requirement, they don't explain why the requirement exists.

The JLS mentions why in §15.27.2:

The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.

To lower the risk of bugs, they decided to ensure captured variables are never mutated. I am confused by the statement that it would lead to concurrency problems.

我在 Baeldung 上阅读了有关并发问题的文章但是,我仍然对它如何导致并发问题感到有些困惑,任何人都可以帮我举个例子。 提前致谢。

最佳答案

我想通过说明我在下面展示的实际上不是 lambda 的实现方式来作为这个答案的开头。实际实现涉及 java.lang.invoke.LambdaMetafactory 如果我没记错的话。我的回答使用了一些不准确的地方来更好地证明这一点


假设您有以下内容:

public static void main(String[] args) {
  String foo = "Hello, World!";
  Runnable r = () -> System.out.println(foo);
  r.run();
}

请记住,lambda 表达式是声明功能接口(interface)实现的简写形式。 lambda 主体是所述功能接口(interface)的单个​​抽象方法的实现。在运行时创建一个实际对象。所以上面的结果是一个对象,它的类实现了 Runnable

现在,上面的 lambda 主体从封闭方法中引用了一个局部变量。作为 lambda 表达式的结果创建的实例“捕获”该局部变量的值。它几乎(但不是真的)就像你有以下内容:

public static void main(String[] args) {
  String foo = "Hello, World!";

  final class GeneratedClass implements Runnable {
    
    private final String generatedField;

    private GeneratedClass(String generatedParam) {
      generatedField = generatedParam;
    }

    @Override
    public void run() {
      System.out.println(generatedField);
    }
  }

  Runnable r = new GeneratedClass(foo);
  r.run();
}

现在应该更容易看到支持并发的问题了:

  1. 局部变量不被视为“共享变量”。这在 §17.4.1 of the Java Language Specification 中说明:

    Memory that can be shared between threads is called shared memory or heap memory.

    All instance fields, static fields, and array elements are stored in heap memory. In this chapter, we use the term variable to refer to both fields and array elements.

    Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.

    换句话说,局部变量不在Java的并发规则范围内,不能在线程间共享。

  2. 在源代码级别,您只能访问局部变量。您看不到生成的字段。

我想 Java 可以设计成在 lambda 主体内修改局部变量只写入生成的字段,而在 lambda 主体外修改局部变量只写入局部变量。但正如您可能想象的那样,这会让人感到困惑和违反直觉。根据源代码,您将有两个变量似乎是一个变量。更糟糕的是,这两个变量的值可能会有所不同。

另一种选择是没有生成字段。但请考虑以下几点:

public static void main(String[] args) {
  String foo = "Hello, World!";
  Runnable r = () -> {
    foo = "Goodbye, World!"; // won't compile
    System.out.println(foo);
  }
  new Thread(r).start();
  System.out.println(foo);
}

这里应该发生什么?如果没有生成的字段,则局部变量正在被第二个线程修改。但是局部变量不能在线程之间共享。因此,这种方法是不可能的,至少在不对 Java 和 JVM 进行可能的重要更改的情况下是不可能的。

因此,据我所知,设计者制定了局部变量必须是 final 或实际上是 final 的规则,以避免并发问题并使开发人员陷入深奥的问题中。

关于java - 为什么在 lambda 表达式中使用的变量应该是 final 或有效的 final,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68093946/

相关文章:

java - 使用链接列表数据结构制作java电话簿?

python - 如何忽略元组的解压缩部分作为 lambda 的参数?

node.js 变量不在代码块中幸存

concurrency - 并发编程作用于数组中的每个元素

ios - 使用dispatch_group_async的并发代码的性能比单线程版本慢很多

java - 是否必须在aspectJ中实现具体方面

java - 如何给tomcat设置IP地址?

java - 如何仅使用命令行运行 Maven 创建的 jar 文件

java - 如何消除 Eclipse Juno 使用 lambda 表达式时出现的错误?

java - 查找从某个键到某个键的消息,同时能够删除陈旧的键