Java 范围内的异步 lambdas 如何到局部变量

Jos*_*eph 2 java lambda multithreading asynchronous

我有一个方法(可以由不同的线程同时调用)创建一个异步任务并返回一个CompletableFuture. 我想通过将任务与 whenComplete(...) 链接来测量运行任务所需的时间,如下所示:

public CompletableFuture<Result> createTask(...) {
    CompletableFuture<Result> result = ...;
    final long startTime = System.currentTimeMillis();
    result.whenComplete((res, err) -> {
        System.out.println(System.currentTimeMillis() - startTime);
    }
}
Run Code Online (Sandbox Code Playgroud)

我传入的 lambda 将异步执行,我编写的方法也将是多线程的。这个 lambda 能够打印出准确的时间吗?当 lambda 被不同的线程异步执行时,Java 如何处理变量范围?

Joh*_*ica 6

当 lambda 捕获一个变量时,您可以将其视为 lambda 获取该变量值的副本。因为只能捕获 final 或有效的 final 变量,所以 lambda 复制它们是安全的。它们的值不会改变,因此不会有副本与原始变量不同步的危险。

Lambda 不需要用匿名类来实现,但您可以在概念上将它们视为匿名类的语法糖。你的whenComplete电话相当于:

long startTime = System.currentTimeMillis();

result.whenComplete(new BiConsumer<T, U>() {
    @Override public void accept(T res, U err) {
        System.out.println(System.currentTimeMillis() - startTime);
    }
});
Run Code Online (Sandbox Code Playgroud)

问题是,变量捕获对于 lambda 并不新鲜。匿名类也捕获变量,它们在 lambda 出现之前就已经做到了。发生的事情是,他们秘密地隐藏了捕获变量的副本。他们获得合成私有字段来存储这些隐藏的值。上面的代码实际上更像这样,如果我们使合成字段显式:

long startTime = System.currentTimeMillis();

result.whenComplete(new BiConsumer<T, U>() {
    private final long _startTime = startTime;

    @Override public void accept(T res, U err) {
        System.out.println(System.currentTimeMillis() - _startTime);
    }
});
Run Code Online (Sandbox Code Playgroud)

请注意,一旦这个匿名BiConsumer被实例化,它就站在自己的两只脚上。accept()now的主体指的是一个实例变量,而不是捕获的变量。匿名对象不绑定到外部函数,也不绑定到创建它的线程。accept()可以在任何时间从任何线程调用,并且会像人们预期的那样运行,即使原始startTime变量早已死亡并被掩埋。