background
- With the rapid development of hardware, multi-core CPU and distributed software architecture are common;
- Functional API s mostly link the content of basic services in a mixed way, which is convenient for users' life.
Two questions were raised:
- How to develop multi-core capability;
- Divide large tasks and let each sub task run in parallel;
The difference between concurrent and parallel
project | Difference 1 | implementation technique |
---|---|---|
parallel | Each task runs on a separate cpu core | Branch merge framework, parallel flow |
Concurrent | Different tasks share cpu core and schedule based on time slice | CompletableFuture |
Future interface
java5 has been introduced. Model what happens at some point in the future.
Perform an asynchronous calculation and return a reference to the result of the operation. When the operation is finished, the reference can be returned to the caller.
You can use Future to put potential time-consuming tasks into asynchronous threads and let the main thread continue to perform other valuable work without waiting for nothing.
Here is an example: with Future, you can run two tasks concurrently, and then aggregate the results;
package com.test.completable; import com.google.common.base.Stopwatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Note: Future application example * @author carter * Creation time: 10:53, November 18, 2019 **/ public class FutureTest { static final ExecutorService pool = Executors.newFixedThreadPool(2); public static void main(String[] args) { Stopwatch stopwatch = Stopwatch.createStarted(); Future<Long> longFuture = pool.submit(() -> doSomethingLongTime()); doSomething2(); try { final Long longValue = longFuture.get(3, TimeUnit.SECONDS); System.out.println(Thread.currentThread().getName() + " future return value :" + longValue + " : " + stopwatch.stop()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } pool.shutdown(); } private static void doSomething2() { Stopwatch stopwatch = Stopwatch.createStarted(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " doSomething2 :" + stopwatch.stop()); } private static Long doSomethingLongTime() { Stopwatch stopwatch = Stopwatch.createStarted(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " doSomethingLongTime : " + stopwatch.stop()); return 1000L; } }
Can't write concurrent code for profile. Insufficient description ability; for example, the following scenarios:
- Merge the results of two asynchronous calculations into one. The two asynchronous calculations are independent of each other, but the second one depends on the first one.
- Wait for all tasks in Future to be completed;
- Only wait for the fastest ending task in the Future collection to complete, and return its result;
- Complete the execution of a Future task by programming;
- Respond to the completion event for Future.
Based on this defect, java8 introduces the completabilefuture class;
Implement asynchronous API
Skill points:
- Provide asynchronous API;
- Modify synchronous API to asynchronous API, how to use pipeline to merge two tasks into one asynchronous computing operation;
- Respond to the completion event of asynchronous operation;
type | Difference | Is it blocked? |
---|---|---|
Synchronous API | The caller waits in the process of being called to run. The callee returns after running. The caller obtains the return value and continues running | blocking |
Asynchronous API | The caller and the callee are asynchronous, and the caller does not have to wait for the callee to return the result | Non clogging |
package com.test.completable; import com.google.common.base.Stopwatch; import com.google.common.base.Ticker; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * Description: call the method of calculating price asynchronously * @author carter * Creation time: 13:32, November 18, 2019 **/ public class Test { public static void main(String[] args) { Shop shop = new Shop("BestShop"); Stopwatch stopwatch = Stopwatch.createStarted(); Stopwatch stopwatch2 = Stopwatch.createStarted(); Future<Double> doubleFuture = shop.getPriceFuture("pizza"); System.out.println("getPriceFuture return after: " + stopwatch.stop()); doSomethingElse(); try{ final Double price = doubleFuture.get(); System.out.println("price is " + price + " return after: " + stopwatch2.stop()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } private static void doSomethingElse() { Stopwatch stopwatch = Stopwatch.createStarted(); DelayUtil.delay(); System.out.println("doSomethingElse " + stopwatch.stop()); } }
error handling
If there is an error in the method of calculating the price, the exception that prompts the error will now be within the scope of the current thread trying to calculate the price of the goods, and the asynchronous thread of the final calculation will be killed, which will cause the client returning the result of the get method to be permanently waiting.
How to avoid the exception being covered, completeExceptionally will throw out the problems occurred in completefuture.
private static void test2() { Shop shop = new Shop("BestShop"); Stopwatch stopwatch = Stopwatch.createStarted(); Stopwatch stopwatch2 = Stopwatch.createStarted(); Future<Double> doubleFuture = shop.getPriceFutureException("pizza"); System.out.println("getPriceFuture return after: " + stopwatch.stop()); doSomethingElse(); try{ final Double price = doubleFuture.get(); System.out.println("price is " + price + " return after: " + stopwatch2.stop()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
Method transformation:
//Query product price asynchronously and throw out exceptions public Future<Double> getPriceFutureException(String product){ final CompletableFuture<Double> doubleCompletableFuture = new CompletableFuture<>(); new Thread(()->{try { doubleCompletableFuture.complete(alculatePriceException(product)); }catch (Exception ex){ doubleCompletableFuture.completeExceptionally(ex); } }).start(); return doubleCompletableFuture; }
No blockage
That is, multiple threads are allowed to execute tasks asynchronously, in parallel or in parallel, and the results are aggregated after calculation;
private static void test3(String productName) { Stopwatch stopwatch = Stopwatch.createStarted(); final List<String> stringList = Stream.of(new Shop("Huaqiang North"), new Shop("Yitian Holiday Plaza"), new Shop("Kowloon City, Hong Kong"), new Shop("Jingdong mall")) .map(item -> String.format("Shop:%s Products:%s The selling price is:%s", item.getName(), productName, item.getPrice(productName))) .collect(Collectors.toList()); System.out.println(stringList); System.out.println("test3 done in " + stopwatch.stop()); } private static void test3_parallelStream(String productName) { Stopwatch stopwatch = Stopwatch.createStarted(); final List<String> stringList = Stream.of(new Shop("Huaqiang North"), new Shop("Yitian Holiday Plaza"), new Shop("Kowloon City, Hong Kong"), new Shop("Jingdong mall")) .parallel() .map(item -> String.format("Shop:%s Products:%s The selling price is:%s", item.getName(), productName, item.getPrice(productName))) .collect(Collectors.toList()); System.out.println(stringList); System.out.println("test3_parallelStream done in " + stopwatch.stop()); } private static void test3_completableFuture(String productName) { Stopwatch stopwatch = Stopwatch.createStarted(); final List<String> stringList = Stream.of(new Shop("Huaqiang North"), new Shop("Yitian Holiday Plaza"), new Shop("Kowloon City, Hong Kong"), new Shop("Jingdong mall")) .map(item ->CompletableFuture.supplyAsync(()-> String.format("Shop:%s Products:%s The selling price is:%s", item.getName(), productName, item.getPrice(productName)))) .collect(Collectors.toList()) .stream() .map(CompletableFuture::join) .collect(Collectors.toList()); System.out.println(stringList); System.out.println("test3_completableFuture done in " + stopwatch.stop()); } private static void test3_completableFuture_pool(String productName) { Stopwatch stopwatch = Stopwatch.createStarted(); final List<String> stringList = Stream.of(new Shop("Huaqiang North"), new Shop("Yitian Holiday Plaza"), new Shop("Kowloon City, Hong Kong"), new Shop("Jingdong mall")) .map(item ->CompletableFuture.supplyAsync(()-> String.format("Shop:%s Products:%s The selling price is:%s", item.getName(), productName, item.getPrice(productName)),pool)) .collect(Collectors.toList()) .stream() .map(CompletableFuture::join) .collect(Collectors.toList()); System.out.println(stringList); System.out.println("test3_completableFuture done in " + stopwatch.stop()); }
There is a simple calculation scenario in the code, I want to query the iPhone 11 prices of four stores;
Huaqiangbei, Yitian Apple store, Kowloon City, Hong Kong, Jingdong Mall;
Each query takes about 1s;
Task handling method | time consuming | Advantages and disadvantages |
---|---|---|
Sequential execution | More than 4 seconds. | Simple, easy to understand |
Parallel flow | More than a second. | Unable to customize the thread pool built in the stream, easy to use and simple to transform |
Completable future default thread pool | More than 2 seconds. | Default thread pool |
Completable future specifies the thread pool | More than a second. | Thread pool is specified, which is more customizable than parallel flow |
Pipeline operation of multiple asynchronous tasks
Scenario: first calculate the price, then get the discount, and finally calculate the discount price;
private static void test4(String productName) { Stopwatch stopwatch = Stopwatch.createStarted(); final List<String> stringList = Stream.of(new Shop("Huaqiang North"), new Shop("Yitian Holiday Plaza"), new Shop("Kowloon City, Hong Kong"), new Shop("Jingdong mall")) .map(shop->shop.getPrice_discount(productName)) .map(Quote::parse) .map(DisCount::applyDiscount) .collect(Collectors.toList()); System.out.println(stringList); System.out.println("test4 done in " + stopwatch.stop()); } private static void test4_completableFuture(String productName) { Stopwatch stopwatch = Stopwatch.createStarted(); final List<String> stringList = Stream.of(new Shop("Huaqiang North"), new Shop("Yitian Holiday Plaza"), new Shop("Kowloon City, Hong Kong"), new Shop("Jingdong mall")) .map(shop->CompletableFuture.supplyAsync(()->shop.getPrice_discount(productName),pool)) .map(future->future.thenApply( Quote::parse)) .map(future->future.thenCompose(quote -> CompletableFuture.supplyAsync(()->DisCount.applyDiscount(quote),pool))) .collect(Collectors.toList()) .stream() .map(CompletableFuture::join) .collect(Collectors.toList()); System.out.println(stringList); System.out.println("test4_completableFuture done in " + stopwatch.stop()); }
The above is the aggregation of two dependent tasks, task 2 and task 1. The ncompose method is used;
Next, if there are two tasks that can be executed asynchronously, the final result depends on the results of the two tasks;
//Two different tasks, finally need to aggregate the results, using combine private static void test5(String productName) { Stopwatch stopwatch = Stopwatch.createStarted(); Shop shop = new Shop("Kowloon, Hongkong"); Double pricefinal = CompletableFuture.supplyAsync(()->shop.getPrice(productName)) .thenCombine(CompletableFuture.supplyAsync(shop::getRate),(price, rate)->price * rate).join(); System.out.println("test4 done in " + stopwatch.stop()); }
completion event
Let the task end as soon as possible without waiting;
There are multiple service sources. If you request more than one, whoever returns first will respond first;
The results are returned in turn:
//Wait for all tasks to complete; completabilefuture. Allof() public void findPriceStream(String productName){ List<Shop> shops = Arrays.asList(new Shop("Huaqiang North"), new Shop("Yitian Holiday Plaza"), new Shop("Kowloon City, Hong Kong"), new Shop("Jingdong mall")); final CompletableFuture[] completableFutureArray = shops.stream() .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice_discount(productName), pool)) .map(future -> future.thenApply(Quote::parse)) .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync(() -> DisCount.applyDiscount(quote), pool))) .map(f -> f.thenAccept(System.out::println)) .toArray(size -> new CompletableFuture[size]); CompletableFuture.allOf(completableFutureArray).join(); }
Get the fastest results from multiple sources:
//There are two ways to get the weather, which is the fastest and which is the final result public static void getWeather(){ final Object join = CompletableFuture.anyOf(CompletableFuture.supplyAsync(() -> a_weather()), CompletableFuture.supplyAsync(() -> b_weather())).join(); System.out.println(join); } private static String b_weather() { DelayUtil.delay(3); return "bWeather"; } private static String a_weather() { DelayUtil.delay(5); return "aWeather"; }
Source code analysis
Complete future;
First look at the signature:
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {}
The interface of Futrue and completionstage is implemented;
These two interfaces are briefly described as follows:
Interface | Key characteristics |
---|---|
Future | Directly translate it into the future, mark to execute a task asynchronously. If necessary, get it through get method, or cancel it. In addition, it also provides status query method, isdone, iscanceled. The implementation class is FutureTask |
CompletionStage | Direct translation is the stage of completion, which provides a functional programming method |
It can be divided into the following methods
Method | Explain |
---|---|
thenApply(Function f) | After the normal completion of the current stage, a new stage is returned, in which the results of the current stage are input as parameters; |
thenConsume(Consumer c), | After the current stage is completed, the result is input as a parameter and consumed directly to get the completion stage without returning the result; |
thenRun(Runnable action), | Do not accept the parameters, just continue to perform the task and get a new completion stage; |
thenCombine(otherCompletionStage,BiFunction), | When both completion phases are completed, execute BIFunction to return to a new phase; |
thenAcceptBoth(OtherCompletionStage, BiConsumer) | After the completion of the two stages, the two results are consumed; |
runAfterBoth(OtherCompletionStage,Runable) | After the completion of both stages, perform an action; |
applyToEither(OtherCompletionStage,Function) | Either of the two completion phases ends, enters the function operation, and returns a new phase |
acceptEither(OtherCompletionStage,Consumer) | The completion stage in which any of the two completion stages is completed, consumed, and a null return value is returned |
runAfterEither(OtherCompletionStage,Runable) | At the end of any of the two completion phases, an action is executed to return a completion phase with a null return value |
thenCompose(Function) | The current stage is completed, the return value is taken as the parameter, the function operation is carried out, and then the result is taken as a new completion stage |
exceptionally(Function) | No matter whether the current phase is completed normally or not, consume the exception, and then return the value as a new completion phase |
whenComplete | |
handle | Whether the current completion phase ends normally or not, a function of BIFunction is executed and a new result is returned as a new completion phase |
toCompletableFuture | Convert to completabilefuture |
The implementation details in it will be discussed in a separate article later.
Summary
- To perform some time-consuming operations, especially those dependent on one or more remote services, asynchronous tasks can be used to improve the performance of programs and speed up the response of programs;
- With completable future, you can easily implement asynchronous API;
- Completable future provides an exception management mechanism, which gives the main thread the opportunity to receive the exception thrown by the pipe task;
- Encapsulating the synchronous API into the completable future can get its results asynchronously;
- If asynchronous tasks are independent of each other, and some of their results are other inputs, these tasks can be combined;
- You can register a callback function for the task in completabilefuture, and perform other operations after the task is executed;
- You can decide when to finish running the program. It means that all the completabilefuture tasks are finished, or any one of them is finished.
Original is not easy, reprint please indicate the source.