我有一个方法(可以由不同的线程同时调用),它创建一个异步任务并返回CompletableFuture
。我想通过使用whenComplete(...)链接它来测量运行任务所需的时间,如下所示:
public CompletableFuture<Result> createTask(...) {
CompletableFuture<Result> result = ...;
final long startTime = System.currentTimeMillis();
result.whenComplete((res, err) -> {
System.out.println(System.currentTimeMillis() - startTime);
}
}
我传入的lambda将以异步方式执行,我编写的方法也将是多线程的。这个lambda能打印出准确的时间吗?当lambda由另一个线程异步执行时,Java如何处理变量范围?
答案 0 :(得分:4)
当lambda捕获变量时,您可以将其视为lambda获取该变量值的副本。因为只能捕获最终或有效的最终变量,所以lambdas可以安全地复制它们。它们的值不会改变,因此副本不会与原始变量不同步。
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);
}
});
事实上,变量捕获对于lambdas来说并不新鲜。匿名类也捕获变量,并且在lambdas出现之前就已经完成了。会发生什么,他们秘密地藏匿捕获变量的副本。他们获得合成私有字段来存储那些存储的值。如果我们将合成字段显式化,上面的代码实际上更像是这样:
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);
}
});
请注意,一旦这个匿名BiConsumer
被实例化,它就会站在自己的两只脚上。 accept()
的主体现在引用实例变量,而不是捕获的变量。匿名对象不依赖于外部函数,也不依赖于创建它的线程。 accept()
可以在任何时间从任何线程调用,并且可以按照预期的方式运行,即使原始的startTime
变量已经很久没有被埋葬了。