Detailed usage of CompletableFuture

The multi-threading opening method supported by JAVA

According to the official Java documentation issued by Oracle, there are only two ways to create threads: inherit Thread or implement the Runnable interface . But there is a flaw in both of these methods, there is no return value, which means we cannot know the thread execution result. Although it is satisfied in simple scenarios, what should we do when we need to return a value? The Callable and Future interfaces after Java 1.5 solve this problem. We can obtain a Future object containing the return value by submitting a Callable to the thread pool. From then on, our program logic is no longer in a synchronous order.

The following is the original text of the Java8 combat book:

The Future interface was introduced in Java5, and its original design is to model the results that will be produced at some point in the future. It models an asynchronous operation, returns a reference to the execution result, and when the operation is complete, the reference is returned to the caller. Trigger completion of those potentially time-consuming operations in the Future.
As shown in the figure below: We have changed from the initial serial operation to parallel operation. While asynchronous, we can also do other things to save program running time.

Image source: Detailed explanation of Java's Future mechanism - know almost

The witty friends will definitely ask, what, isn't this article about the new feature of JAVA8, CompletableFuture? How do you talk about Future? no hurry, see below

Limitations of the Future interface

When we get the Future containing the result, we can use the get method to wait for the thread to complete and get the return value. Note that the get()  method of Future will block the main thread where I bolded . The original text of the Future document is as follows

A {@code Future} represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation.

Google Translate:

{@code Future} represents the result of an asynchronous* computation. Methods are provided to check if a computation is complete, to wait for it to complete, and to retrieve the result of the computation.

Future performs time-consuming tasks

From this we know that before the Future gets the thread execution result, our main thread get() needs to block and wait for the result. Even if we use the isDone() method to poll to check the thread execution status, this is a waste of cpu resources .

 

Image source: Java8 actual combat

When the Future thread performs a very time-consuming operation, our main thread will also be blocked. When we are in simple business, we can use another overloaded method get(long, TimeUnit) of Future to set the timeout period to avoid our main thread being blocked endlessly. However, is there a better solution?

We need more powerful asynchronous capabilities

Not only that, when we encounter a business scenario, simply using the Future interface or the FutureTask class cannot well complete the following business we need

  • Combine two asynchronous computations into one, where the two asynchronous computations are independent of each other, while the second depends on the result of the first
  • Wait for all tasks in the Future collection to complete.
  • Just wait for the fastest-ending task in the Future collection to complete (possibly because they tried to compute the same value in different ways), and return its result.
  • Complete the execution of a Future task programmatically (that is, manually set the result of an asynchronous operation).
  • Respond to the completion time of the Future (that is, when the completion time of the Future is completed, you will be notified, and you can use the calculation results of the Future to perform the next operation, not just simply blocking the result of waiting for the operation)

text

Magical CompletableFuture

What is CompletableFuture

In Java 8, a new class with about 50 methods has been added: CompletableFuture, which combines the advantages of Future and provides a very powerful extension function of Future, which can help us simplify the complexity of asynchronous programming and provide functional programming The ability to process calculation results through callbacks, and provides methods for converting and combining CompletableFutures.

CompletableFuture is designed for asynchronous programming in Java. Asynchronous programming means creating an independent thread outside the main thread, separated from the main thread, and running a non-blocking task on it, and then notifying the main thread of progress, success or failure.

In this way, your main thread does not have to block/wait for the completion of the task, and you can use the main thread to perform other tasks in parallel. Using this parallel method greatly improves the performance of the program.

Java8 source code doc comment :

 Translation :

When a Future may need to be explicitly completed, use the CompletionStage interface to support functions and operations triggered on completion.
When two or more threads attempt to complete, complete with exception, or cancel a CompletableFuture at the same time, only one can succeed.
 
CompletableFuture implements the following strategies of the CompletionStage interface:
 
1. In order to complete the thread of the callback function of the current CompletableFuture interface or other completion methods, a non-asynchronous completion operation is provided.
 
2. All async methods that do not explicitly enter the Executor use ForkJoinPool.commonPool(). In order to simplify monitoring, debugging and tracking,
    all generated asynchronous tasks are instances of the marker interface AsynchronousCompletionTask.     3. All CompletionStage methods are implemented independently of other public methods, so the behavior of a method will not be overridden by other methods
 
in subclasses . CompletableFuture implements the following strategies of the Future interface: 1. CompletableFuture cannot directly control the completion, so the cancel operation is regarded as another form of abnormal completion.     The method isCompletedExceptionally can be used to determine whether a CompletableFuture completed in any abnormal way. 2. Taking a CompletionException as an example, the methods get() and get(long, TimeUnit) throw an ExecutionException,

 

 


 

    Corresponds to CompletionException. To simplify usage in most contexts, this class also defines the methods join() and getNow
    instead of directly throwing CompletionException in these cases.

CompletableFuture API

Friends who want to find examples directly to get started can skip to the back

Instantiate CompletableFuture

Instantiation method

  1. public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);

  2. public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);

  3. public static CompletableFuture<Void> runAsync(Runnable runnable);

  4. public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);

There are two formats, one is the method beginning with supply, and the other is the method beginning with run

  • Start of supply: This method can return the result after the execution of the asynchronous thread
  • Start of run: this will not return the result, just execute the thread task

Or you can pass a simple no-argument constructor

CompletableFuture<String> completableFuture = new CompletableFuture<String>();

Tips : We have noticed that in the instantiation method, we can specify the Executor parameter. When we do not specify the test, the parallel threads we open use the default system and public thread pool ForkJoinPool, and these threads They are all daemon threads. We need to use daemon threads carefully when programming. If we set our ordinary user threads as daemon threads, when the main thread of our program ends and there are no other user threads in the JVM, then the daemon thread of CompletableFuture will exit directly, causing task The problem that cannot be completed, and the rest including the daemon thread blocking problem, I will not repeat it in this article.

Java8 actual combat:

Among them, supplyAsync is used for tasks with return values, and runAsync is used for tasks without return values. The Executor parameter can manually specify the thread pool, otherwise the system-level common thread pool of ForkJoinPool.commonPool() is defaulted. Note: These threads are all Daemon threads. The Daemon thread does not end when the main thread ends. Only when the JVM is closed, the life cycle ends.

get results

Get results synchronously

public T    get()
public T    get(long timeout, TimeUnit unit)
public T    getNow(T valueIfAbsent)
public T    join()

simple example

CompletableFuture<Integer> future = new CompletableFuture<>();
Integer integer = future.get();

The get()  method will also block until the task is completed. In the above code, the main thread will always block because the future created in this way has never been completed. Interested friends can make a breakpoint to see, the status will always be not completed

The first two methods are relatively easy to understand. Those who read the Future section above must know what they mean. getNow()  is different. The parameter valueIfAbsent means that when the calculation result does not exist or the task is not completed at the Now moment, a certain value is given.

 The difference between join()  and get() is that join()  returns the calculated result or throws an unchecked exception (CompletionException), while get()  returns a specific exception.

After the calculation is completed, follow-up operation 1——complete

public CompletableFuture<T>     whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T>     whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T>     whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T>     exceptionally(Function<Throwable,? extends T> fn)

The difference between methods 1 and 2 is whether to use asynchronous processing, and the difference between 2 and 3 is whether to use a custom thread pool. The first three methods will provide a return result and an exception that can be thrown . We can use lambda expressions to receive These two parameters are then handled by themselves. Method 4, receive a throwable exception, and must return a return value, the type is the same as that of the diamond expression, see the exceptionally()  section below for more details

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            return 10086;
        });
        future.whenComplete((result, error) -> {
            System.out.println("拨打"+result);
            error.printStackTrace();
        });

After the calculation is completed, follow-up operation 2——handle

public <U> CompletableFuture<U>     handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U>     handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U>     handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)

Sharp-eyed friends may have discovered that the handle method set is no different from the above complete method set. There are also two parameters, a return result and a throwable exception . The difference lies in the return value. Yes, although it also returns the CompletableFuture type, But the parameter type and handle method inside can be customized.

// 开启一个异步方法
        CompletableFuture<List> future = CompletableFuture.supplyAsync(() -> {
            List<String> list = new ArrayList<>();
            list.add("语文");
            list.add("数学");
            // 获取得到今天的所有课程
            return list;
        });
        // 使用handle()方法接收list数据和error异常
        CompletableFuture<Integer> future2 = future.handle((list,error)-> {
            // 如果报错,就打印出异常
            error.printStackTrace();
            // 如果不报错,返回一个包含Integer的全新的CompletableFuture
            return list.size();
             // 注意这里的两个CompletableFuture包含的返回类型不同
        });

Subsequent operation 3 after the calculation is completed——apply

public <U> CompletableFuture<U>     thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U>     thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U>     thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

Why are these three methods called, the follow-up operation 2 after the calculation is completed, because the apply method is the same as the handle method, which is a follow-up operation after the end of the calculation. The only difference is that the handle method will give an exception, allowing the user to It is processed internally, and the apply method has only one return result . If there is an exception, it will be thrown directly and handed over to the upper layer for processing. If you don't want to handle exceptions for every chain call, then use apply.

Example : See the exceptionally()  example below

Subsequent operation 4 after the calculation is completed——accept

public CompletableFuture<Void>  thenAccept(Consumer<? super T> action)
public CompletableFuture<Void>  thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void>  thenAcceptAsync(Consumer<? super T> action, Executor executor)

The accept() method only consumes the final result. Note that the CompletableFuture returned at this time is empty. Only consumption, no return, a bit like the terminal operation of streaming programming .

Example : See the exceptionally()  example below

Catch the exception generated in the middle - exceptionally

public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)

exceptionally()  can help us catch all intermediate process exceptions. The method will give us an exception as a parameter. We can handle this exception and return a default value, which is a bit like service degradation. The type  of the default value is the same as that of the previous operation. The return value is the same. Tips  : The exception that occurs when submitting a task to the thread pool is an external exception and cannot be caught, after all, the task has not yet been executed. The author also discovered it when the thread pool rejection policy was triggered. exceptionally()  cannot catch RejectedExecutionException()

// 实例化一个CompletableFuture,返回值是Integer
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            // 返回null
            return null;
        });
 
        CompletableFuture<String> exceptionally = future.thenApply(result -> {
            // 制造一个空指针异常NPE
            int i = result;
            return i;
        }).thenApply(result -> {
            // 这里不会执行,因为上面出现了异常
            String words = "现在是" + result + "点钟";
            return words;
        }).exceptionally(error -> {
            // 我们选择在这里打印出异常
            error.printStackTrace();
            // 并且当异常发生的时候,我们返回一个默认的文字
            return "出错啊~";
        });
 
        exceptionally.thenAccept(System.out::println);
 
    }

final output

Compositional asynchronous programming

Combining two completableFutures

Remember what we said above that Future can't do it?

  • Combines two asynchronous computations into one, where the two asynchronous computations are independent of each other, while the second depends on the result of the first.

thenApply()

Assuming a scenario, I am a primary school student, and I want to know how many courses I need to take today. At this time, I need two steps, 1. Obtain my student information according to my name 2. Query courses according to my student information We can use The following method is used to call the API in a chain, and use the result of the previous step to proceed to the next step

CompletableFuture<List<Lesson>> future = CompletableFuture.supplyAsync(() -> {
            // 根据学生姓名获取学生信息
            return StudentService.getStudent(name);
        }).thenApply(student -> {
            // 再根据学生信息获取今天的课程
            return LessonsService.getLessons(student);
        });

 

We get the student information according to the student's name, and then use the obtained student information student to pass to the apply()  method to get the student's course list today.

  • Combine two asynchronous calculations into one, these two asynchronous calculations are independent of each other and independent of each other

thenCompose()

Assuming a scenario, I am a primary school student, and today I have labor skills and art classes, I need to find out what I need to bring to school today

CompletableFuture<List<String>> total = CompletableFuture.supplyAsync(() -> {
            // 第一个任务获取美术课需要带的东西,返回一个list
            List<String> stuff = new ArrayList<>();
            stuff.add("画笔");
            stuff.add("颜料");
            return stuff;
        }).thenCompose(list -> {
            // 向第二个任务传递参数list(上一个任务美术课所需的东西list)
            CompletableFuture<List<String>> insideFuture = CompletableFuture.supplyAsync(() -> {
                List<String> stuff = new ArrayList<>();
                // 第二个任务获取劳技课所需的工具
                stuff.add("剪刀");
                stuff.add("折纸");
                // 合并两个list,获取课程所需所有工具
                List<String> allStuff = Stream.of(list, stuff).flatMap(Collection::stream).collect(Collectors.toList());
                return allStuff;
            });
            return insideFuture;
        });
        System.out.println(total.join().size());

We  create the first task through the CompletableFuture.supplyAsync() method, obtain the list of items required for the art class, and then use the thenCompose()  interface to pass the list to the second task, and then the second task obtains the items required for the labor class , and return after integration. So far we have completed the merger of the two tasks. (To be honest, it seems a bit awkward to use compose to realize this business scenario, let's look at the next example)

  • Combine two asynchronous calculations into one, these two asynchronous calculations are independent of each other and independent of each other

thenCombine()

Still in the scene above, I am a primary school student, and today I have labor skills class and art class, I need to find out what I need to bring to school today

CompletableFuture<List<String>> painting = CompletableFuture.supplyAsync(() -> {
            // 第一个任务获取美术课需要带的东西,返回一个list
            List<String> stuff = new ArrayList<>();
            stuff.add("画笔");
            stuff.add("颜料");
            return stuff;
        });
        CompletableFuture<List<String>> handWork = CompletableFuture.supplyAsync(() -> {
            // 第二个任务获取劳技课需要带的东西,返回一个list
            List<String> stuff = new ArrayList<>();
            stuff.add("剪刀");
            stuff.add("折纸");
            return stuff;
        });
        CompletableFuture<List<String>> total = painting
                // 传入handWork列表,然后得到两个CompletableFuture的参数Stuff1和2
                .thenCombine(handWork, (stuff1, stuff2) -> {
                    // 合并成新的list
                    List<String> totalStuff = Stream.of(stuff1, stuff1)
                            .flatMap(Collection::stream)
                            .collect(Collectors.toList());
                    return totalStuff;
                });
        System.out.println(JSONObject.toJSONString(total.join()));
  • Waits for all tasks in the Future collection to complete.

Get all completed results - allOf

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

allOf method, returns a brand new completed CompletableFuture when all given tasks have completed

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                //使用sleep()模拟耗时操作
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        });
 
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            return 2;
        });
        CompletableFuture.allOf(future1, future1);
        // 输出3
        System.out.println(future1.join()+future2.join());
Get the result of the task completed first - anyOf
  • Just wait for the fastest-ending task in the Future collection to complete (possibly because they tried to compute the same value in different ways), and return its result. Tips  : If there is an exception in the fastest completed task, the exception will be returned first. If you are afraid of making mistakes, you can add exceptionally()  to handle possible exceptions and set the default return value
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            throw new NullPointerException();
        });
 
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                // 睡眠3s模拟延时
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        });
        CompletableFuture<Object> anyOf = CompletableFuture
                .anyOf(future, future2)
                .exceptionally(error -> {
                    error.printStackTrace();
                    return 2;
                });
        System.out.println(anyOf.join());

a few small examples

Combination of multiple methods

  • Complete the execution of a Future task programmatically (that is, manually set the result of an asynchronous operation).
  • Respond to the completion time of the Future (that is, when the completion time of the Future is completed, you will be notified, and you can use the calculation results of the Future to perform the next operation, not just simply blocking the result of waiting for the operation)
public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> 1)
                .whenComplete((result, error) -> {
                    System.out.println(result);
                    error.printStackTrace();
                })
                .handle((result, error) -> {
                    error.printStackTrace();
                    return error;
                })
                .thenApply(Object::toString)
                .thenApply(Integer::valueOf)
                .thenAccept((param) -> System.out.println("done"));
    }

Create concurrent tasks in a loop

public static void main(String[] args) {
        long begin = System.currentTimeMillis();
        // 自定义一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // 循环创建10个CompletableFuture
        List<CompletableFuture<Integer>> collect = IntStream.range(1, 10).mapToObj(i -> {
            CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
                // 在i=5的时候抛出一个NPE
                if (i == 5) {
                    throw new NullPointerException();
                }
                try {
                    // 每个依次睡眠1-9s,模拟线程耗时
                    TimeUnit.SECONDS.sleep(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
                return i;
            }, executorService)
                    // 这里处理一下i=5时出现的NPE
                    // 如果这里不处理异常,那么异常会在所有任务完成后抛出,小伙伴可自行测试
                    .exceptionally(Error -> {
                        try {
                            TimeUnit.SECONDS.sleep(5);
                            System.out.println(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return 100;
                    });
            return future;
        }).collect(Collectors.toList());
        // List列表转成CompletableFuture的Array数组,使其可以作为allOf()的参数
        // 使用join()方法使得主线程阻塞,并等待所有并行线程完成
        CompletableFuture.allOf(collect.toArray(new CompletableFuture[]{})).join();
        System.out.println("最终耗时" + (System.currentTimeMillis() - begin) + "毫秒");
        executorService.shutdown();
    }

Using CompletableFuture scenarios

  • When performing time-consuming operations, especially those that depend on one or more remote services, using asynchronous tasks can improve the performance of the program and speed up the response of the program
  • Use the CompletableFuture class, which provides an exception management mechanism, giving you the opportunity to throw and manage exceptions that occur during asynchronous task execution
  • If these asynchronous tasks are independent of each other, or the result of some of them is the input of others, you can construct or combine these asynchronous tasks into one

Tips  : Do not use the JUit unit test for multi-thread testing, because JUnit will close the JVM after the main thread is completed. Interested friends, please Baidu

Guess you like

Origin blog.csdn.net/weixin_42218169/article/details/131192865