1, Completable future asynchronous orchestration
1. Business scenario
The logic of querying the product details page is complex, and some data needs to be called remotely, which will inevitably take more time.
If each query of the product details page needs the time indicated below to be completed, then the user needs 5.5s to see the content of the product details page. Obviously, it is unacceptable. If multiple threads complete these six steps at the same time, it may only take 1.5s to complete the response.
2,java8 - CompletableFuture
In Java 8, a new class containing about 50 methods is added: completable Future, which provides a very powerful extension function of Future, which can help us simplify the complexity of asynchronous programming, provide the ability of functional programming, process the calculation results through callback, and provide the method of converting and combining completable Future. The completabilefuture class implements the Future interface, so you can still get results by blocking or polling the get method as before, but this method is not recommended.
Completabilefuture and FutureTask belong to the implementation class of the Future interface. They can get the execution results of threads.
2.1 creating asynchronous objects
Completable future provides four static methods to create an asynchronous operation.
-
runXxxx does not return results, and supplyXxx can obtain the returned results
-
You can pass in a custom thread pool, otherwise the default thread pool will be used;
2.2 runAsync method - no return value
@Slf4j public class Test01 { public static ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) { log.info("main...start..."); CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { log.info("Current thread:{}",Thread.currentThread().getName()); }, executor); log.info("main...end..."); } }
Print results:
2.3 supplyAsync method - with return value
@Slf4j public class Test01 { public static ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> { log.info("Current thread:{}", Thread.currentThread().getId()); int i = 10 / 2; log.info("Operation results:{}", i); return i; }, executor); // Get results Integer res = supplyAsync.get(); log.info("Get results:{}",res); } }
Print results:
2.4 callback method upon completion of calculation
whenComplete can handle normal and abnormal calculation results, and exceptionally handle abnormal situations.
The difference between whenComplete and whenCompleteAsync:
whenComplete: the thread that executes the current task executes the task that continues to execute whenComplete.
whenCompleteAsync: the task of whenCompleteAsync is submitted to the thread pool for execution.
@Slf4j public class Test01 { public static ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> { log.info("Current thread:{}", Thread.currentThread().getId()); int i = 10 / 0; log.info("Operation results:{}", i); return i; }, executor).whenComplete((res,exception)->{ log.info("The asynchronous task completed successfully, and the result is:{},The exception is:{}",res,exception); }).exceptionally(t->{ // It can sense the exception and return the default value at the same time return 10; }); Integer integer = supplyAsync.get(); log.info("Get results:{}",integer); } }
Print results:
2.5 handle method
Like complete, the result can be finally processed (exceptions can be handled) and the return value can be changed.
/** * Processing after method execution */ CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> { log.info("Current thread:{}", Thread.currentThread().getId()); int i = 10 / 5; log.info("Operation results:{}", i); return i; }, executor).handle((res,thr) ->{ // If the return result of the previous step is not empty, the return result will be multiplied by 2 if(res != null){ return res*2; } // If the exception is not empty, it indicates that an exception occurs, and 0 is returned if(thr != null){ return 0; } return 0; }); Integer integer = supplyAsync.get(); log.info("Get results:{}",integer);
2.6 thread serialization method
thenApply method: when a thread depends on another thread, get the result returned by the previous task and return the return value of the current task.
thenAccept method: consume processing results. Receive the processing result of the task and consume it. No result is returned.
thenRun method: execute thenRun as long as the above task is completed. Only after processing the task, the subsequent operations of thenRun are Async. By default, it is asynchronous, that is, start a new thread to execute the asynchronous task. Same as before.
-
thenRun and thenRunAsync methods: the execution result of the previous step cannot be obtained, and there is no return value
// thenRun method CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> { log.info("Current thread:{}", Thread.currentThread().getId()); int i = 10 / 5; log.info("Operation results:{}", i); return i; }, executor).thenRun(() -> { log.info("Perform task 2"); }); // thenRunAsync: you can specify your own thread pool CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> { log.info("Current thread:{}", Thread.currentThread().getId()); int i = 10 / 5; log.info("Operation results:{}", i); return i; }, executor).thenRunAsync(()->{ log.info("Perform task 2...."); },executor);
-
thenAcceptAsync: you can get the execution result of the previous step, but there is no return value
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> { log.info("Current thread:{}", Thread.currentThread().getId()); int i = 10 / 5; log.info("Operation results:{}", i); return i; }, executor).thenAcceptAsync(res->{ log.info("Perform task 2...Execution result of the previous step:{}",res); },executor);
Print results:
-
thenApplyAsync: you can get the execution result of the previous step with a return value, and the execution result of the following task is the return value
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { log.info("Current thread:{}", Thread.currentThread().getId()); int i = 10 / 5; log.info("Operation results:{}", i); return i; }, executor).thenApplyAsync(res -> { log.info("Task 2 begins...."); return "hello " + res; }, executor); log.info("Final return result:{}",future.get());
2.7 combination of two tasks - both to be completed
-
runAfterBothAsync
When combining two futures, you don't need to obtain the results of the future. You only need to process the task after the two futures process the task
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> { log.info("Task 1 starts...."); int i = 10 / 5; log.info("Task 1 running result:{}", i); return i; }, executor); CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> { log.info("Task 2 starts...."); log.info("End of task 2...."); return "hello"; }, executor); // After completing task 1 and task 2, execute task 3 future01.runAfterBothAsync(future02,()->{ log.info("Task 3 starts...."); },executor);
Print result: because task 1 and task 2 are executed asynchronously, there is no order of execution, but task 3 must be executed after the completion of the two tasks
-
thenAcceptBothAsync: you can get the return results of the first two tasks
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> { log.info("Task 1 starts...."); int i = 10 / 5; log.info("Task 1 running result:{}", i); return i; }, executor); CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> { log.info("Task 2 starts...."); log.info("End of task 2...."); return "hello"; }, executor); // You can get the return results of the first two tasks future01.thenAcceptBothAsync(future02,(f1,f2)->{ log.info("Task 3: obtain the results of the first two tasks:{}--->{}",f1,f2); },executor);
Print results:
- Thencombinesync: it can accept the return values of the first two tasks and have return values
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> { log.info("Task 1 starts...."); int i = 10 / 5; log.info("Task 1 running result:{}", i); return i; }, executor); CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> { log.info("Task 2 starts...."); log.info("End of task 2...."); return "hello"; }, executor); CompletableFuture<String> future = future01.thenCombineAsync(future02, (f1, f2) -> { return f1 + " : " + f2 + "haha!"; }, executor); log.info("Results returned from task 3:{}",future.get());
Print results:
-
runAfterEitherAsync: for two tasks, as long as one is completed, it will execute task 3. It does not perceive the result and returns its own business value
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> { log.info("Task 1 starts...."); int i = 10 / 5; log.info("Task 1 running result:{}", i); return i; }, executor); CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> { log.info("Task 2 starts...."); try { Thread.sleep(3000); log.info("End of task 2...."); } catch (InterruptedException e) { e.printStackTrace(); } return "hello"; }, executor); future01.runAfterEitherAsync(future02,()->{ log.info("Task 3..."); },executor); log.info("mian....end....");
Print results:
-
acceptEitherAsync: perception result, no return value
CompletableFuture<Object> future01 = CompletableFuture.supplyAsync(() -> { log.info("Task 1 starts...."); int i = 10 / 5; log.info("Task 1 running result:{}", i); return i; }, executor); CompletableFuture<Object> future02 = CompletableFuture.supplyAsync(() -> { log.info("Task 2 starts...."); try { Thread.sleep(3000); log.info("End of task 2...."); } catch (InterruptedException e) { e.printStackTrace(); } return "hello"; }, executor); future01.acceptEitherAsync(future02,(res)->{ log.info("Task 3 starts and returns the following results:{}",res); },executor);
Print results:
-
applyToEitherAsync: perceives the result and has a return value
CompletableFuture<Object> future01 = CompletableFuture.supplyAsync(() -> { log.info("Task 1 starts...."); int i = 10 / 5; log.info("Task 1 running result:{}", i); return i; }, executor); CompletableFuture<Object> future02 = CompletableFuture.supplyAsync(() -> { log.info("Task 2 starts...."); try { Thread.sleep(3000); log.info("End of task 2...."); } catch (InterruptedException e) { e.printStackTrace(); } return "hello"; }, executor); CompletableFuture<String> future = future01.applyToEitherAsync(future02, (res) -> { log.info("Task 3 execution..."); return res.toString() + "haha"; }, executor); log.info("Result returned from task 3:{}",future.get());
Print results:
2.8 multi task combination
allOf: wait for all tasks to complete
anyOf: as long as one task is completed