Java CompletableFuture Demo

1.Introduction

When working with asynchronous operations with Java,the CompletableFuture class offers a way to manage these operations in a more intuitive and structured manner.Two commonly used methods in this class for handling multiple asynchronous tasks are allOf()and join().In the artcle,we will study these two methods.

2.Combining Multiple CompletableFuture

2.1 Useing CompletableFuture.allOf().join()

The allOf()methods in CompletableFuture allows you to combine multiple asynchronous tasks and create a new CompletableFuture that completes when all of the input futures are completed.The join() methods is then used to block the current thread until all the input futures are completed.This is particularly useful when you have a collection of tasks that need to executed concurrently,and you want to wait for all of then to complete before proceeding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static void allOfDemo() {
//是用CompletableFuture的allOf方法实现一个例子
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 10);
//join方法用来阻塞当前线程,等待future1 future2 计算完成
CompletableFuture.allOf(future1, future2).join();
try {
Integer integer = future1.get();
Integer integer1 = future2.get();
System.out.println("result1: " + (integer));
System.out.println("result2: " + (integer1));
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}

2.2 Useing CompletableFuture.allOf().join() with multithreading

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private static void allDemoThread(){
ExecutorService executor1 = Executors.newSingleThreadExecutor();
ExecutorService executor2 = Executors.newSingleThreadExecutor();
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000); // 休眠3秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return 5;
}, executor1);

CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000); // 休眠3秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return 10;
}, executor2);

CompletableFuture.allOf(future1, future2).join();

try {
Integer integer = future1.get();
Integer integer1 = future2.get();
System.out.println("result1: " + (integer));
System.out.println("result2: " + (integer1));
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} finally {
executor1.shutdown();
executor2.shutdown();
}
}

2.3 Using CompletableFuture.join()

On the other hand, the join() method on a single CompletableFuture is used to block the current thread and retrieve the result when the future is completed. This is useful when you have a single task that you want to wait for and obtain the result from. It’s important to note that unlike allOf(), join() is not used for combining multiple futures but for waiting on a single future.

1
2
3
4
5
 public static void main(String[] args) {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42);
int result = future.join();
System.out.println("Result: " + result);
}

Error Handling

3.1 Handling Exceptions in CompletableFuture.allOf().join()

When using CompletableFuture.allOf().join(), exceptions that occur in any of the input futures are not directly propagated to the calling thread. Instead, the exceptions are typically stored within the individual futures. To handle exceptions in this scenario, you’ll need to retrieve them from the completed futures.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static void AllOfErrorHandlerDemo() {
//处理CompletableFuture.allOf().join()中的异常,使用CompletableFuture.allOf().join()方法等待所有的CompletableFuture完成
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 5)
.exceptionally(ex -> {
System.err.println("Exception in future1: " + ex);
return 0; // Default value or recovery logic
});
CompletableFuture<Object> future2 = CompletableFuture.supplyAsync(() -> {
//异常
throw new RuntimeException("Something went wrong");
}).exceptionally(ex -> {
//异常处理
System.err.println("Exception in future2: " + ex);
return 0;
});
// 返回一个新的CompletableFuture,当所有的CompletableFuture都完成后,新的CompletableFuture完成
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);
// join()方法等待future1和future2完成,然后打印结果
combinedFuture.join();
try {
int result1 = future1.get();
int result2 = (int) future2.get();
System.out.println("Result 1: " + result1);
System.out.println("Result 2: " + result2);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}

In this example, we’re using the .exceptionally() method to handle exceptions for each individual future. This allows us to log the exception and provide a default value or recovery logic if needed.

4 Conclusion

In conclusion,both CompletableFuture.allOf().join() and CompletableFuture.join() are valuable tools in the asynchronous programming toolkit. They serve different purposes: allOf().join() is for combining multiple futures and waiting for their completion, while join() is for waiting on a single future. Understanding their use cases, behavior, and performance implications can greatly enhance your ability to write efficient and responsive asynchronous code in Java.

Java CompletableFuture Demo