头图

In daily development, we all use thread pools, and generally use the execute() and submit() methods to submit tasks. But when you have used CompletableFuture, you will find how difficult and simple the previous thread pool processing tasks are, and how simple and elegant CompletableFuture is.

To know that CompletableFuture has been released with Java 8 for 7 years, it is a bit unreasonable to not have it.
Today, I will take you a practical tutorial of CompletableFuture in 5 minutes.

1. Use a thread pool to process tasks

 /**
 * @author yideng
 * @apiNote 线程池使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        // 1. 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        
        List<Integer> list = Arrays.asList(1, 2, 3);
        List<Future<String>> futures = new ArrayList<>();
        for (Integer key : list) {
            // 2. 提交任务
            Future<String> future = executorService.submit(() -> {
                // 睡眠一秒,模仿处理过程
                Thread.sleep(1000L);
                return "结果" + key;
            });
            futures.add(future);
        }

        // 3. 获取结果
        for (Future<String> future : futures) {
            try {
                String result = future.get();
                System.out.println(result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
    }

}

Output result:

 结果1
结果2
结果3

Generally, everyone will use the thread pool in this way, but have you ever thought about whether there is any problem with this use?
Anyway, I found two more serious problems:

  1. When the result is obtained, the future.get() method called will block the current thread until the result is returned, which greatly reduces performance
  2. Half of the code is writing how to use threads. In fact, we should not care about how to use threads, but more about the processing of tasks.

Is there any specific optimization plan? Of course there is, please come out, our protagonist today, CompletableFuture

2. Use CompletableFuture to refactor task processing

Take a look at the modified code using CompletableFuture:

 /**
 * @author yideng
 * @apiNote CompletableFuture使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        // 1. 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        List<Integer> list = Arrays.asList(1, 2, 3);
        for (Integer key : list) {
            // 2. 提交任务
            CompletableFuture.supplyAsync(() -> {
                // 睡眠一秒,模仿处理过程
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                }
                return "结果" + key;
            }, executorService).whenCompleteAsync((result, exception) -> {
                // 3. 获取结果
                System.out.println(result);
            });;
        }

        executorService.shutdown();
        // 由于whenCompleteAsync获取结果的方法是异步的,所以要阻塞当前线程才能输出结果
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

Output result:

 结果1
结果2
结果3

Two methods of CompletableFuture are used in the code,
The role of the supplyAsync() method is to submit asynchronous tasks. There are two parameters, the task and the custom thread pool.
The function of the whenCompleteAsync() method is to obtain the result asynchronously, and it also has two parameters, the result and the exception information.

After the code has been transformed by CompletableFuture, how concise and elegant it is.
You don't need to care about how the thread pool is used when submitting tasks, and you don't need to block the current thread for getting results.

If you are stubborn and want to get the result synchronously, you can use the whenComplete() method, or call the join() method separately.
The join() method is used with Stream streams like this:

 /**
 * @author yideng
 * @apiNote CompletableFuture使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        // 1. 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        List<Integer> list = Arrays.asList(1, 2, 3);
        // 2. 提交任务
        List<String> results = list.stream().map(key ->
                CompletableFuture.supplyAsync(() -> {
                    // 睡眠一秒,模仿处理过程
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                    }
                    return "结果" + key;
                }, executorService))
                .map(CompletableFuture::join).collect(Collectors.toList());

        executorService.shutdown();
        // 3. 获取结果
        System.out.println(results);

    }

}

Output result:

 [结果1,结果2,结果3]

How simple and elegant! The original executorService.submit() way of using the thread pool can be completely lost.

3. CompletableFuture More Magical Uses

3.1 Wait for all tasks to complete

If you were asked to wait for all task threads to complete before proceeding to the next step, what would you do?
I guess you will definitely use thread pool + CountDownLatch like this:

 /**
 * @author yideng
 * @apiNote 线程池和CountDownLatch使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        // 1. 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        List<Integer> list = Arrays.asList(1, 2, 3);
        CountDownLatch countDownLatch = new CountDownLatch(list.size());
        for (Integer key : list) {
            // 2. 提交任务
            executorService.execute(() -> {
                // 睡眠一秒,模仿处理过程
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                }
                System.out.println("结果" + key);
                countDownLatch.countDown();
            });
        }

        executorService.shutdown();
        // 3. 阻塞等待所有任务执行完成
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
        }
    }

}

Output result:

 结果2
结果3
结果1

Low or not? It was possible to write like this ten years ago, and Java8 has been released for 7 years. You still don't know how to write it in Java8? Take a look at how this is refactored to use CompletableFuture:

 /**
 * @author yideng
 * @apiNote CompletableFuture.allOf()方法使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        // 1. 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        List<Integer> list = Arrays.asList(1, 2, 3);
        // 2. 提交任务,并调用join()阻塞等待所有任务执行完成
        CompletableFuture
                .allOf(
                        list.stream().map(key ->
                                CompletableFuture.runAsync(() -> {
                                    // 睡眠一秒,模仿处理过程
                                    try {
                                        Thread.sleep(1000L);
                                    } catch (InterruptedException e) {
                                    }
                                    System.out.println("结果" + key);
                                }, executorService))
                                .toArray(CompletableFuture[]::new))
                .join();
        executorService.shutdown();
    }

}

Output result:

 结果3
结果1
结果2

The code looks a bit messy, but the logic is very clear.

  1. Traverse the list collection, submit the CompletableFuture task, and convert the result into an array
  2. Then put the array into the allOf() method of CompletableFuture
  3. Finally, call the join() method to block and wait for all tasks to complete.

The role of the allOf() method of CompletableFuture is to wait for all tasks to complete.
Is it a lot more concise and elegant to write this way?

3.2 Return when any task is processed

If you want to achieve such a requirement, submit a batch of tasks to the thread pool, and return as long as one of the tasks is processed.
How to do? If you implement this logic manually, the code must be complex and inefficient. With CompletableFuture, it is very simple, just call the anyOf() method.

 /**
 * @author yideng
 * @apiNote CompletableFuture.anyOf()方法使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        // 1. 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        List<Integer> list = Arrays.asList(1, 2, 3);
        long start = System.currentTimeMillis();
        // 2. 提交任务
        CompletableFuture<Object> completableFuture = CompletableFuture
                .anyOf(
                        list.stream().map(key ->
                                CompletableFuture.supplyAsync(() -> {
                                    // 睡眠一秒,模仿处理过程
                                    try {
                                        Thread.sleep(1000L);
                                    } catch (InterruptedException e) {
                                    }
                                    return "结果" + key;
                                }, executorService))
                                .toArray(CompletableFuture[]::new));
        executorService.shutdown();

        // 3. 获取结果
        System.out.println(completableFuture.join());
    }

}

Output result:

 结果3

Everything is so simple and elegant.

3.3 After a thread is executed, it is handed over to another thread for execution

There is such a requirement:
After one thread has finished processing, the result of the processing is handed over to another thread to continue processing. How to achieve this?

Have you thought of a bunch of tools, thread pool, CountDownLatch, Semaphore, ReentrantLock, Synchronized, how to use them in combination? AB combination or BC combination?

Don't think about it, what you wrote is definitely not as easy to use as CompletableFuture, let's see how CompletableFuture is used:

 /**
 * @author yideng
 * @apiNote CompletableFuture线程接力处理示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        // 1. 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // 2. 提交任务,并调用join()阻塞等待任务执行完成
        String result2 = CompletableFuture.supplyAsync(() -> {
            // 睡眠一秒,模仿处理过程
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
            }
            return "结果1";
        }, executorService).thenApplyAsync(result1 -> {
            // 睡眠一秒,模仿处理过程
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
            }
            return result1 + "结果2";
        }, executorService).join();

        executorService.shutdown();
        // 3. 获取结果
        System.out.println(result2);
    }

}

Output result:

 结果1结果2

The code mainly uses the thenApplyAsync() method of CompletableFuture, which is used to asynchronously process the result of the previous thread.

Is it too convenient?

Is there any other function of such a useful CompletableFuture? Of course there is.

4. CompletableFuture common API

4.1 CompletableFuture Common API Description

  1. Submit a task
    supplyAsync
    runAsync
  2. Relay processing

    thenRun thenRunAsync
    thenAccept thenAcceptAsync
    thenApply thenApplyAsync
    handle handleAsync
    applyToEither applyToEitherAsync
    acceptEither acceptEitherAsync
    runAfterEither runAfterEitherAsync
    thenCombine thenCombineAsync
    thenAcceptBoth thenAcceptBothAsync

There are so many APIs, it's a bit dizzying and easy to categorize.
A method with run has no input parameters and no return value.
A method with accept has input parameters and no return value.
The method with supply has no input parameters and has a return value.
The method with apply has input parameters and return value.
The method with handle has input parameters, return value, and exception handling.
Methods ending with Async are asynchronous, otherwise they are synchronous.
For methods ending in Either, simply complete either.
Methods ending in Both/Combine must complete all of them.

  1. get results
    join blocks waiting, no exceptions are thrown
    get blocks waiting and throws an exception
    complete(T value) does not block, if the task has completed, returns the processing result. If not completed, return the passed parameter value.
    completeExceptionally(Throwable ex) does not block and returns the processing result if the task has completed. If not completed, throw an exception.

4. CompletableFuture common API usage example

Take the most common cooking example as an example:

4.1 Example of using then and handle methods

 /**
 * @author yideng
 * @apiNote then、handle方法使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("1. 开始淘米");
            return "2. 淘米完成";
        }).thenApplyAsync(result -> {
            System.out.println(result);
            System.out.println("3. 开始煮饭");
            // 生成一个1~10的随机数
            if (RandomUtils.nextInt(1, 10) > 5) {
                throw new RuntimeException("4. 电饭煲坏了,煮不了");
            }
            return "4. 煮饭完成";
        }).handleAsync((result, exception) -> {
            if (exception != null) {
                System.out.println(exception.getMessage());
                return "5. 今天没饭吃";
            } else {
                System.out.println(result);
                return "5. 开始吃饭";
            }
        });

        try {
            String result = completableFuture.get();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

The output might be:

 1. 开始淘米
2. 淘米完成
3. 开始煮饭
4. 煮饭完成
5. 开始吃饭

It could also be:

 1. 开始淘米
2. 淘米完成
3. 开始煮饭
java.lang.RuntimeException: 4. 电饭煲坏了,煮不了
5. 今天没饭吃

4.2 Example of using the complete method

 /**
 * @author yideng
 * @apiNote complete使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            return "饭做好了";
        });
        
        //try {
        //    Thread.sleep(1L);
        //} catch (InterruptedException e) {
        //}

        completableFuture.complete("饭还没做好,我点外卖了");
        System.out.println(completableFuture.join());
    }

}

Output result:

 饭还没做好,我点外卖了

If you release the commented sleep() method, the output will be:

 饭做好了

4.3 Example of using either method

 /**
 * @author yideng
 * @apiNote either方法使用示例
 */
public class ThreadDemo {

    public static void main(String[] args) {
        CompletableFuture<String> meal = CompletableFuture.supplyAsync(() -> {
            return "饭做好了";
        });
        CompletableFuture<String> outMeal = CompletableFuture.supplyAsync(() -> {
            return "外卖到了";
        });

        // 饭先做好,就吃饭。外卖先到,就吃外卖。就是这么任性。
        CompletableFuture<String> completableFuture = meal.applyToEither(outMeal, myMeal -> {
            return myMeal;
        });

        System.out.println(completableFuture.join());
    }

}

The output might be:

 饭做好了

It could also be:

 外卖到了

Did you learn it? Hurry up and use it in development!


一灯架构
44 声望11 粉丝