Foreword
In our consciousness, synchronous execution of the program are more in line with the way people think, but is usually not a good thing asynchronous processing. In the case of an asynchronous computation operation callback represented tend dispersed in the code, it may be nested inside each other, if required a step wherein the processing error may occur, the situation becomes worse. Java 8 introduces many new features, which will contain the CompletableFuture introduction class, which makes us easier to write asynchronous code is legible, the class is very powerful, containing more than 50 methods. . .
What is CompletableFuture
CompletableFuture
Class design was inspired by Google Guava
the ListenableFuture class that implements Future
and CompletionStage
the interface and added a number of methods, which supports lambda, through the use of non-blocking callback method to enhance the asynchronous programming model. It allows us to pass on the main application thread with a different thread (ie asynchronous) to run the task, and the progress of the main thread notification task completion or failure to write non-blocking code.
Why introduce CompletableFuture
Java
The 1.5 release introduces Future
, you can put it simply understood as the result of the operation of a placeholder, it provides two methods to get the result of the operation.
get()
: This method is called thread will wait indefinitely for the result of the operation.get(long timeout, TimeUnit unit)
: This method is called thread only at a specified timetimeout
to wait for results within, if the wait timeout will throwTimeoutException
an exception.
Future
You may be used Runnable
or Callable
instance submitted to the task, its source code can be seen, as it exists several problems:
- Blocking call
get()
method will block until wait until the calculation is complete, it does not provide any method can be notified of completion, it also does not have the additional function callback function. - Chained calls polymerization process and results , in many cases we would like to link more
Future
to complete the lengthy calculations, this time need to consolidate the results and send the results to another task, the interface is difficult to complete this process. - Exception handling
Future
does not provide any way of exception handling.
These problems CompletableFuture
are already solved, let's look at how to use CompletableFuture
.
How to create CompletableFuture
The easiest way is to call the create CompletableFuture.completedFuture(U value)
method to obtain a completed CompletableFuture
object.
@Test
public void testSimpleCompletableFuture() {
CompletableFuture<String> completableFuture = CompletableFuture.completedFuture("Hello mghio");
assertTrue(completableFuture.isDone());
try {
assertEquals("Hello mghio", completableFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
Note that if we do not complete the CompleteableFuture
call get
, it will be because the method Future
is not complete, so the get
call will block forever, then you can use CompletableFuture.complete
the manual method to complete Future
.
Asynchronous processing tasks
When we want to deal with the results of the program to perform tasks asynchronously in the background without concern for the task, you can use the runAsync
method, which receives a Runnable
parameter of type return CompletableFuture<Void>
.
@Test
public void testCompletableFutureRunAsync() {
AtomicInteger variable = new AtomicInteger(0);
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> process(variable));
runAsync.join();
assertEquals(100, variable.get());
}
public void process(AtomicInteger variable) {
System.out.println(Thread.currentThread() + " Process...");
variable.set(100);
}
If we want to perform tasks in the background and asynchronous processing results need to get the job, you can use supplyAsync
the method, the method receives a Supplier<T>
type of a parameter to return CompletableFuture<T>
.
@Test
public void testCompletableFutureSupplyAsync() {
CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process);
try {
assertEquals("Hello mghio", supplyAsync.get()); // Blocking
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
public String process() {
return "Hello mghio";
}
See here you may have a problem, perform above runAsync
and supplyAsync
threaded task is to come from, who created it? It is actually 8 and Java parallelStream
similar, CompletableFuture
but also from the global ForkJoinPool.commonPool()
to perform these tasks thread obtained. Meanwhile, the above two methods also provide a custom thread pool to perform the task, in fact, if you go to find out about CompletableFuture
the source code, you will find that API
all methods have overloaded versions, with or without custom Executor
execution device.
@Test
public void testCompletableFutureSupplyAsyncWithExecutor() {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process, newFixedThreadPool);
try {
assertEquals("Hello mghio", supplyAsync.get()); // Blocking
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
public String process() {
return "Hello mghio";
}
Chain polymerization process calls and results
We know that CompletableFuture
the get()
method will remain 阻塞
until get to the results, CompletableFuture
providing thenApply
, thenAccept
and thenRun
other methods to avoid this situation, but we can also add a callback notification after the task is completed. These methods use scenario is as follows:
- thenApply when if we are to from
Future
when you run the business after receiving the custom code value before the task, and then return some value to this task, you can use this method - thenAccept If we want to from
Future
time after receiving a number of business value code to run custom tasks before the results do not care about the return value, you can use this method - thenRun If we want business code in the Future finished running custom, and do not want to do this any return value, you can use this method
@Test
public void testCompletableFutureThenApply() {
Integer notificationId = CompletableFuture.supplyAsync(this::thenApplyProcess)
.thenApply(this::thenApplyNotify) // Non Blocking
.join();
assertEquals(new Integer(1), notificationId);
}
@Test
public void testCompletableFutureThenAccept() {
CompletableFuture.supplyAsync(this::processVariable)
.thenAccept(this::thenAcceptNotify) // Non Blocking
.join();
assertEquals(100, variable.get());
}
@Test
public void testCompletableFutureThenRun() {
CompletableFuture.supplyAsync(this::processVariable)
.thenRun(this::thenRunNotify)
.join();
assertEquals(100, variable.get());
}
private String processVariable() {
variable.set(100);
return "success";
}
private void thenRunNotify() {
System.out.println("thenRun completed notify ....");
}
private Integer thenApplyNotify(Integer integer) {
return integer;
}
private void thenAcceptNotify(String s) {
System.out.println(
String.format("Thread %s completed notify ....", Thread.currentThread().getName()));
}
public Integer thenApplyProcess() {
return 1;
}
If a large number of asynchronous computation, so we can continue to deliver value to a callback from the callback to another, that is, using a chained call mode, it uses very simple.
@Test
public void testCompletableFutureThenApplyAccept() {
CompletableFuture.supplyAsync(this::findAccountNumber)
.thenApply(this::calculateBalance)
.thenApply(this::notifyBalance)
.thenAccept((i) -> notifyByEmail()).join();
}
private void notifyByEmail() {
// business code
System.out.println("send notify by email ...");
}
private Double notifyBalance(Double d) {
// business code
System.out.println(String.format("your balance is $%s", d));
return 9527D;
}
private Double calculateBalance(Object o) {
// business code
return 9527D;
}
private Double findAccountNumber() {
// business code
return 9527D;
}
Compare Careful friends may have noticed that in all the previous examples of several methods, all methods are executed on the same thread. If we want these tasks to run on a separate thread, then we can use these methods corresponding to the asynchronous version.
@Test
public void testCompletableFutureApplyAsync() {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
ScheduledExecutorService newSingleThreadScheduledExecutor = Executors
.newSingleThreadScheduledExecutor();
CompletableFuture<Double> completableFuture =
CompletableFuture
.supplyAsync(this::findAccountNumber,
newFixedThreadPool) // 从线程池 newFixedThreadPool 获取线程执行任务
.thenApplyAsync(this::calculateBalance,
newSingleThreadScheduledExecutor)
.thenApplyAsync(this::notifyBalance);
Double balance = completableFuture.join();
assertEquals(9527D, balance);
}
Results of the implementation process
thenCompose
Methods suitable for the task dependency treatment, such as an account balance of business computing: First, we must first find an account, and then calculate the balance of the account, then the calculation is complete before sending notification. All of these tasks are dependent on the previous job return CompletableFuture
results, then we need to use the thenCompose
method, in fact, somewhat similar to Java 8 stream flatMap
operations.
@Test
public void testCompletableFutureThenCompose() {
Double balance = this.doFindAccountNumber()
.thenCompose(this::doCalculateBalance)
.thenCompose(this::doSendNotifyBalance).join();
assertEquals(9527D, balance);
}
private CompletableFuture<Double> doSendNotifyBalance(Double aDouble) {
sleepSeconds(2);
// business code
System.out.println(String.format("%s doSendNotifyBalance ....", Thread.currentThread().getName()));
return CompletableFuture.completedFuture(9527D);
}
private CompletableFuture<Double> doCalculateBalance(Double d) {
sleepSeconds(2);
// business code
System.out.println(String.format("%s doCalculateBalance ....", Thread.currentThread().getName()));
return CompletableFuture.completedFuture(9527D);
}
private CompletableFuture<Double> doFindAccountNumber() {
sleepSeconds(2);
// business code
System.out.println(String.format("%s doFindAccountNumber ....", Thread.currentThread().getName()));
return CompletableFuture.completedFuture(9527D);
}
private void sleepSeconds(int timeout) {
try {
TimeUnit.SECONDS.sleep(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
thenCombine
The method is mainly used to combine the processing results of a plurality of separate tasks. Suppose we need to find a person's name and address, you can use different tasks to obtain respectively, and then get the results you want complete information on the person's (name + address), you need to merge the two approaches, then we can use the thenCombine
method .
@Test
public void testCompletableFutureThenCombine() {
CompletableFuture<String> thenCombine = this.findName().thenCombine(this.findAddress(), (name, address) -> name + address);
String personInfo = thenCombine.join();
assertEquals("mghio Shanghai, China", personInfo);
}
private CompletableFuture<String> findAddress() {
return CompletableFuture.supplyAsync(() -> {
sleepSeconds(2);
// business code
return "Shanghai, China";
});
}
private CompletableFuture<String> findName() {
return CompletableFuture.supplyAsync(() -> {
sleepSeconds(2);
// business code
return "mghio ";
});
}
Wait for execution to complete multiple tasks
In many cases, we want to run multiple tasks in parallel, and then perform some processing all tasks completed. Suppose we want to find three different user name and combine the results. At this point you can use the CompletableFuture
static method allOf
, the method waits until all tasks are completed, should be noted that this method does not return all the tasks it combined results, so the results we need a combination of manual tasks.
@Test
public void testCompletableFutureAllof() {
List<CompletableFuture<String>> list = Lists.newArrayListWithCapacity(4);
IntStream.range(0, 3).forEach(num -> list.add(findName(num)));
CompletableFuture<Void> allFuture = CompletableFuture
.allOf(list.toArray(new CompletableFuture[0]));
CompletableFuture<List<String>> allFutureList = allFuture
.thenApply(val -> list.stream().map(CompletableFuture::join).collect(Collectors.toList()));
CompletableFuture<String> futureHavingAllValues = allFutureList
.thenApply(fn -> String.join("", fn));
String result = futureHavingAllValues.join();
assertEquals("mghio0mghio1mghio2", result);
}
private CompletableFuture<String> findName(int num) {
return CompletableFuture.supplyAsync(() -> {
sleepSeconds(2);
// business code
return "mghio" + num;
});
}
Exception Handling
In fact, an exception is not a good deal in a multithreaded program, but fortunately in CompletableFuture
the provided us with a very convenient way of handling exception, in our example above code:
@Test
public void testCompletableFutureThenCompose() {
Double balance = this.doFindAccountNumber()
.thenCompose(this::doCalculateBalance)
.thenCompose(this::doSendNotifyBalance).join();
}
In the above code, the three methods doFindAccountNumber
, doCalculateBalance
and doSendNotifyBalance
as long as any abnormality occurs, the following method call will not run.
CompletableFuture
Providing three ways to handle exceptions, respectively exceptionally
, handle
and whenComplete
methods. The first way is to use the exceptionally
method to handle exceptions, if the previous method fails and an exception occurs, the exception callback is called.
@Test
public void testCompletableFutureExceptionally() {
CompletableFuture<Double> thenApply = CompletableFuture.supplyAsync(this::findAccountNumber)
.thenApply(this::calculateBalance)
.thenApply(this::notifyBalance)
.exceptionally(ex -> {
System.out.println("Exception " + ex.getMessage());
return 0D;
});
Double join = thenApply.join();
assertEquals(9527D, join);
}
The second way is to use a handle
method handle exceptions, and the way to handle exceptions than the above exceptionally
is more flexible way, we can get to both the current exception object and processing results.
@Test
public void testCompletableFutureHandle() {
CompletableFuture.supplyAsync(this::findAccountNumber)
.thenApply(this::calculateBalance)
.thenApply(this::notifyBalance)
.handle((ok, ex) -> {
System.out.println("最终要运行的代码...");
if (ok != null) {
System.out.println("No Exception !!");
} else {
System.out.println("Exception " + ex.getMessage());
return -1D;
}
return ok;
});
}
A third is to use a whenComplete
method of treating abnormalities.
@Test
public void testCompletableFutureWhenComplete() {
CompletableFuture.supplyAsync(this::findAccountNumber)
.thenApply(this::calculateBalance)
.thenApply(this::notifyBalance)
.whenComplete((result, ex) -> {
System.out.println("result = " + result + ", ex = " + ex);
System.out.println("最终要运行的代码...");
});
}
to sum up
In this paper, we introduce the CompletableFuture
following part of the method and use class methods of this class a lot while providing the functionality is very powerful, more use of asynchronous programming, familiar with the basic usage to understand or to in-depth source analysis and implementation principles.