using Supplier in CompletableFuture yields different result than using lambda

1.1k views Asked by At

I have created a small example of reading a text file and wrap the call with CompletableFuture.

public class Async {
    public static void main(String[] args) throws Exception {
        CompletableFuture<String> result = ReadFileUsingLambda(Paths.get("path/to/file"));
        result.whenComplete((ok, ex) -> {
            if (ex == null) {
                System.out.println(ok);
            } else {
                ex.printStackTrace();
            }
        });
    }

    public static CompletableFuture<String> ReadFileUsingSupplier(Path file) throws Exception {
        return CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                try {
                    return new String(Files.readAllBytes(file));
                } catch (IOException e) {
                    e.printStackTrace();
                    return "test";
                }
            }
        }, ForkJoinPool.commonPool());
    }

    public static CompletableFuture<String> ReadFileUsingLambda(Path file) throws Exception {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return new String(Files.readAllBytes(file));
            } catch (IOException e) {
                e.printStackTrace();
                return "test";
            }
        } , ForkJoinPool.commonPool());
    }
}

This code returns nothing. It executes and "nothing happens", no errors or output. If I call ReadFileUsingSupplier instead of ReadFileUsingLambda then I get the file content printed in the console!

To me this doesn't make sense because a lambda is a shorthand for writing an inline function and it shouldn't change the behaviour but in this example it does apparently.

2

There are 2 answers

4
assylias On BEST ANSWER

I think it's just a matter of execution timing - the lambda may take a little more to execute, allowing the program to exit before you are done reading the file.

Try this:

  • add a Thread.sleep(1000); as the first statement within the try block in ReadFileUsingSupplier and you won't see any output
  • add a Thread.sleep(1000); at the end of your main when using ReadFileUsingLambda and you will see the expected output

To make sure your main doesn't exit before the future is completed, you can call:

result.join();
0
ahri On

As noted, you need to result.join() in either case to avoid the main thread exiting too quickly.

It seems that there's a penalty for using lambdas vs anonymous closures while the JVM warms up, thereafter the performance is the same. I found this information at on another SO thread - which in turn links a performance study by Oracle.

As a sidenote it's not a great idea to Thread.sleep() to fix weird timing issues, ever. Figuring out the cause and applying the appropriate measures would be much clearer when re-read by you or by others, e.g.

System.out.println(result.get(5, TimeUnit.SECONDS));

This enables you to ditch the .join(), too.