Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.0k views
in Technique[技术] by (71.8m points)

multithreading - How do asynchronous lambdas in Java scope to local variables

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<Result> createTask(...) {
    CompletableFuture<Result> 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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<T, U>() {
    @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<T, U>() {
    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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...