I have a method (can be concurrently called by different threads) that creates an asynchronous task and returns a CompletableFuture. I want to measure the time it takes to run the task by chaining it with a whenComplete(...) as follows:
public CompletableFuture createTask(...) {
CompletableFuture result = ...;
final long startTime = System.currentTimeMillis();
result.whenComplete((res, err) -> {
System.out.println(System.currentTimeMillis() - startTime);
}
}
The lambda that I pass in will be executed asynchronously and the method that I wrote will also be multi-threaded. Will this lambda be able to print out the accurate time? How does Java handle variable scope when the lambda gets executed asynchronously by a different thread?
解决方案
When a lambda captures a variable, you can think of it as the lambda getting a copy of that variable's value. Because only final or effectively final variables can be captured, it is safe for lambdas to copy them. Their values won't change, so there's no danger of the copy being out of sync with the original variable.
Lambdas needn't be implemented with anonymous classes, but you can think of them conceptually as syntactic sugar for anonymous classes. Your whenComplete call is equivalent to:
long startTime = System.currentTimeMillis();
result.whenComplete(new BiConsumer() {
@Override public void accept(T res, U err) {
System.out.println(System.currentTimeMillis() - startTime);
}
});
The thing is, variable capturing isn't new with lambdas. Anonymous classes also capture variables, and they did it before lambdas came along. What happens is, they secretly stash away copies of captured variables. They get synthetic private fields to store those stashed values. The code above actually is more like this, if we make the synthetic field explicit:
long startTime = System.currentTimeMillis();
result.whenComplete(new BiConsumer() {
private final long _startTime = startTime;
@Override public void accept(T res, U err) {
System.out.println(System.currentTimeMillis() - _startTime);
}
});
Notice that once this anonymous BiConsumer is instantiated it stands on its own two feet. The body of accept() now refers to an instance variable, not to the captured variable. The anonymous object is not tied to the outer function, nor to the thread in which it was created. accept() can be called at any time and from any thread and will behave as one would expect, even if the original startTime variable is long since dead and buried.