1.Future
When performing multiple tasks, it is very convenient to use the thread pool provided by the Java standard library. The tasks we submit only need to implement the Runnable interface to allow the thread pool to execute:
class Task implements Runnable { public String result; public void run() { this.result = longTimeCalculation(); } }
There is a problem with the Runnable interface. Its method has no return value. If a task needs a return result, it can only be saved to variables, and additional methods are provided to read, which is very inconvenient. Therefore, the Java standard library also provides a Callable interface, which has one more return value than the Runnable interface:
class Task implements Callable<String> { public String call() throws Exception { return longTimeCalculation(); } }
And the Callable interface is a generic interface that can return results of a specified type.
The question now is, how to get the result of asynchronous execution?
If you look closely at the ExecutorService.submit() method, you can see that it returns a Future type, and an instance of Future type represents an object that can obtain results in the Future:
ExecutorService executor = Executors.newFixedThreadPool(4); // Define task: Callable<String> task = new Task(); // Submit tasks and obtain Future: Future<String> future = executor.submit(task); // from Future Gets the result returned by asynchronous execution: String result = future.get(); // May block
When we submit a Callable task, we will obtain a Future object at the same time. Then, we can call the get() method of the Future object at some time in the main thread to obtain the result of asynchronous execution. When we call get(), if the asynchronous task has been completed, we get the result directly. If the asynchronous task has not completed yet, get() will block and will not return the result until the task is completed.
A future < V > interface represents a result that may be returned in the future. It defines the following methods:
- get(): get results (may wait)
- get(long timeout, TimeUnit unit): get the result, but only wait for the specified time;
- cancel(boolean mayInterruptIfRunning): cancels the current task;
- isDone(): judge whether the task has been completed.
Summary
Submit a Callable task to the thread pool to obtain a Future object;
You can use Future to get results at some point in the Future.
2.CompletableFuture
When using Future to obtain asynchronous execution results, either call the blocking method get() or poll to see if isDone() is true. Both methods are not very good because the main thread will also be forced to wait.
Completable Future has been introduced from Java 8. It is improved for the Future. It can pass in the callback object. When the asynchronous task is completed or an exception occurs, the callback method of the callback object will be called automatically.
Let's take the stock price as an example to see how to use completable future:
import java.util.concurrent.CompletableFuture;
public class Main { public static void main(String[] args) throws Exception { // Create asynchronous execution task: CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Main::fetchPrice); // If the execution is successful: cf.thenAccept((result) -> { System.out.println("price: " + result); }); // If execution is abnormal: cf.exceptionally((e) -> { e.printStackTrace(); return null; }); // Don't end the main thread immediately, otherwise CompletableFuture The thread pool used by default will be closed immediately: Thread.sleep(200); } static Double fetchPrice() { try { Thread.sleep(100); } catch (InterruptedException e) { } if (Math.random() < 0.3) { throw new RuntimeException("fetch price failed!"); } return 5 + Math.random() * 20; } }
price: 7.468336731107743
Creating a completabilefuture is implemented through completabilefuture. Supplyasync(). It requires an object that implements the Supplier interface:
public interface Supplier<T> { T get(); }
Here, we simplify it with lambda syntax and directly pass in Main::fetchPrice, because the signature of the static method of Main.fetchPrice() conforms to the definition of the Supplier interface (except for the method name).
Then, completable future has been submitted to the default thread pool for execution. We need to define the instances that need callback when completable future completes and exceptions. When completed, completabilefuture calls the Consumer object:
public interface Consumer<T> { void accept(T t); }
In case of exception, completabilefuture will call the Function object:
public interface Function<T, R> { R apply(T t); }
Here we have simplified the code with lambda syntax.
It can be seen that the advantages of completable future are:
- When the asynchronous task ends, it will automatically call back the method of an object;
- When an asynchronous task makes an error, it will automatically call back the method of an object;
- After setting the callback, the main thread no longer cares about the execution of asynchronous tasks.
If we only implement the asynchronous callback mechanism, we can't see the advantages of completable Future over Future. The more powerful function of completable Future is that multiple completable futures can be executed serially. For example, two completable futures are defined. The first completable Future queries the securities code according to the securities name and the second completable Future queries the securities price according to the securities code. The serial operations of the two completable futures are as follows:
import java.util.concurrent.CompletableFuture;
public class Main { public static void main(String[] args) throws Exception { // First task: CompletableFuture<String> cfQuery = CompletableFuture.supplyAsync(() -> { return queryCode("PetroChina"); }); // cfQuery After success, continue to the next task: CompletableFuture<Double> cfFetch = cfQuery.thenApplyAsync((code) -> { return fetchPrice(code); }); // cfFetch Print results after success: cfFetch.thenAccept((result) -> { System.out.println("price: " + result); }); // Don't end the main thread immediately, otherwise CompletableFuture The thread pool used by default will be closed immediately: Thread.sleep(2000); } static String queryCode(String name) { try { Thread.sleep(100); } catch (InterruptedException e) { } return "601857"; } static Double fetchPrice(String code) { try { Thread.sleep(100); } catch (InterruptedException e) { } return 5 + Math.random() * 20; } }
price: 21.019102834733275
In addition to serial execution, multiple completable future can also be executed in parallel. For example, we consider such a scenario:
At the same time, query the securities code from Sina and NetEase. As long as any one returns the result, you can query the price in the next step. As long as any one returns the result, you can complete the operation:
public class Main { public static void main(String[] args) throws Exception { // Two CompletableFuture Execute asynchronous query: CompletableFuture<String> cfQueryFromSina = CompletableFuture.supplyAsync(() -> { return queryCode("PetroChina", "https://finance.sina.com.cn/code/"); }); CompletableFuture<String> cfQueryFrom163 = CompletableFuture.supplyAsync(() -> { return queryCode("PetroChina", "https://money.163.com/code/"); }); // use anyOf Merge into a new CompletableFuture: CompletableFuture<Object> cfQuery = CompletableFuture.anyOf(cfQueryFromSina, cfQueryFrom163); // Two CompletableFuture Execute asynchronous query: CompletableFuture<Double> cfFetchFromSina = cfQuery.thenApplyAsync((code) -> { return fetchPrice((String) code, "https://finance.sina.com.cn/price/"); }); CompletableFuture<Double> cfFetchFrom163 = cfQuery.thenApplyAsync((code) -> { return fetchPrice((String) code, "https://money.163.com/price/"); }); // use anyOf Merge into a new CompletableFuture: CompletableFuture<Object> cfFetch = CompletableFuture.anyOf(cfFetchFromSina, cfFetchFrom163); // final result: cfFetch.thenAccept((result) -> { System.out.println("price: " + result); }); // Don't end the main thread immediately, otherwise CompletableFuture The thread pool used by default will be closed immediately: Thread.sleep(200); } static String queryCode(String name, String url) { System.out.println("query code from " + url + "..."); try { Thread.sleep((long) (Math.random() * 100)); } catch (InterruptedException e) { } return "601857"; } static Double fetchPrice(String code, String url) { System.out.println("query price from " + url + "..."); try { Thread.sleep((long) (Math.random() * 100)); } catch (InterruptedException e) { } return 5 + Math.random() * 20; } }
query code from https://money.163.com/code/... query code from https://finance.sina.com.cn/code/... query price from https://finance.sina.com.cn/price/... query price from https://money.163.com/price/... price: 6.214906451395034
The asynchronous query rules implemented by the above logic are actually:
┌─────────────┐ ┌─────────────┐ │ Query Code │ │ Query Code │ │ from sina │ │ from 163 │ └─────────────┘ └─────────────┘ │ │ └───────┬───────┘ ▼ ┌─────────────┐ │ anyOf │ └─────────────┘ │ ┌───────┴────────┐ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Query Price │ │ Query Price │ │ from sina │ │ from 163 │ └─────────────┘ └─────────────┘ │ │ └────────┬───────┘ ▼ ┌─────────────┐ │ anyOf │ └─────────────┘ │ ▼ ┌─────────────┐ │Display Price│ └─────────────┘
In addition to anyOf() which can realize "any completable future needs only one success", allOf() which can realize "all completable future must succeed", these combined operations can realize very complex asynchronous process control.
Finally, we pay attention to the naming rules of completable future:
- xxx(): indicates that the method will continue to execute in the existing thread;
- Xxasync(): indicates that asynchronous execution will be performed in the thread pool.
Summary
Completable future can specify asynchronous processing flow:
- thenAccept() processes normal results;
- Exception() handles exception results;
- thenApplyAsync() is used to serialize another completable future;
- anyOf() and allOf() are used to parallelize multiple completable futures.