JDK8引入了大量的新特性和增强如Lambda表达式,Streams,CompletableFuture等。本篇文章结合代码案例讲解下CompletableFuture常见用法。
什么是CompletableFuture?
在Java中CompletableFuture用于异步编程,异步编程是编写非阻塞的代码,运行的任务在一个单独的线程,与主线程隔离,并且会通知主线程它的进度,成功或者失败。
在这种方式中,主线程不会被阻塞,不需要一直等到子线程完成。主线程可以并行的执行其他任务。
使用这种并行方式,可以极大的提高程序的性能。
CompletableFuture相对于传统的Future的优势
1. 可以手动完成调用future.complete()
2.多个 Future 可以串联在一起组成链式调用 执行一个长时间运行的计算任务,并且当计算任务完成的时候,你需要把它的计算结果发送给另外一个长时间运行的计算任务等等
3.Future可以在非阻塞的情况下执行进一步的操作,利用thenApply(),thenAccept() 和 thenRun() API 可以实现。
4.可以组合多个Future结果,使用API:allof(),anyof(),thenCombine(),thenCompose()
5.增加了异常处理API,没有任务的异常处理结构有很多的限制。
1.get() 阻塞获得CompletableFuture的结果;get()
方法会一直阻塞直到 Future 完成。因此,以上的调用将被永远阻塞,因为该Future一直不会完成。
2.runAsync()用于运行异步计算,通常异步运行一个后台任务不需要该任务的返回结果。(注意:这里的Runnable对象采用的是Lambda表达式)
//runAsync() 运行异步计算 异步运行一个后台任务不需要该任务的返回结果
@Test
public void test01() throws ExecutionException, InterruptedException {
CompletableFuture completableFuture = new CompletableFuture<String>();
completableFuture.complete("finished future");
completableFuture.get(); // get 方法会阻塞直到future完成
System.out.println("completableFuture finished");
//Using Lambda expression
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("在独立的子线程运行,非主线程");
});
future.get();
}
3.supplyAsync()用于异步运行并返回结果。当运行异步线程你需要依赖它的返回结果时,就使用该方法。
@Test
public void test02() throws ExecutionException, InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
//模拟耗时的计算线程
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "Result of the asynchronous computation";// 返回异步计算结果
}
});
String result = future.get(); // 阻塞等待线程结束返回结果
System.out.println("result====>" + result);
}
CompletableFuture的转化和组合
4.thenApply() 可以处理和改变CompletableFuture的结果 ,thenAccept() 和 thenRun() 如果你不想从你的回调函数中返回任何东西,仅仅想在Future完成后运行一些代码片段。(如下图)
5.CompletableFuture.allOf() CompletableFuture.allOf
的使用场景是当你一个列表的独立future,并且你想在它们都完成后并行的做一些事情。可以组合多个CompletableFuture的结果集;
最常用的场景是你需要异步处理一个List,并合并处理后的结果集。
//场景: 多线程异步处理List,并合并结果集
@Test
public void test03(){
//模拟数据
List<String> dataList = new ArrayList<>();
for(int i=0;i<100;i++){
dataList.add("Data" + i);
}
List<CompletableFuture<List<String>>> futures = new ArrayList<>();
// 每个CompletableFuture负责查询一部分数据
int batchSize = 20;
for (int i = 0; i < dataList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, dataList.size());
List<String> subList = dataList.subList(i, endIndex);
CompletableFuture<List<String>> future = CompletableFuture.supplyAsync(() -> performQuery(subList));
futures.add(future);
}
//使用allof方法等待所有查询完成
CompletableFuture<Void> allfutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
//合并结果集
// thenApply() 可以使用 thenApply() 处理和改变CompletableFuture的结果
//thenAccept() 和 thenRun() 如果你不想从你的回调函数中返回任何东西,仅仅想在Future完成后运行一些代码片段
CompletableFuture<List<String>> listCompletableFuture = allfutures.thenApply(v -> futures.stream().map(CompletableFuture::join).flatMap(List::stream).collect(Collectors.toList()));
//阻塞等待合并结果
List<String> result = listCompletableFuture.join();
for (String s: result) {
System.out.println("result====>" + s);
}
}
private List<String> performQuery(List<String> subList) {
// 模拟查询耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 返回查询结果
List<String> result = new ArrayList<>();
for (String item : subList) {
result.add("Result for " + item);
}
return result;
}
值得一提的是,我们在处理CompletableFuture结果集时,当所有future完成的时候,我们调用了future.join()
,因此我们不会在任何地方阻塞
6.CompletableFuture.anyOf()
CompletableFuture.anyOf()
和其名字介绍的一样,当任何一个CompletableFuture完成的时候【相同的结果类型】,返回一个新的CompletableFuture
@Test
public void test05() throws ExecutionException, InterruptedException {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 2";
});
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 3";
});
CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2, future3);
System.out.println(anyOfFuture.get()); // Result of Future 2
}
在以上示例中,当三个中的任何一个CompletableFuture完成, anyOfFuture
就会完成。因为future2
的休眠时间最少,因此她最先完成,最终的结果将是future2
的结果。
7.使用thenCombine()
组合两个独立的 future。另外你想从CompletableFuture链中获取一个直接合并后的结果,这时候你可以使用thenCompose()
@Test
public void test04() throws ExecutionException, InterruptedException {
System.out.println("Retrieving weight.");
CompletableFuture<Double> weightInKgFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 72.0;
});
System.out.println("Retrieving height.");
CompletableFuture<Double> heightInCmFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 173.5;
});
System.out.println("Calculating BMI.");
CompletableFuture<Double> combinedFuture = weightInKgFuture
.thenCombine(heightInCmFuture, (weightInKg, heightInCm) -> {
Double heightInMeter = heightInCm/100;
return weightInKg/(heightInMeter*heightInMeter);
});
System.out.println("Your BMI is - " + combinedFuture.get());
}
CompletableFuture 异常处理
我们探寻了怎样创建CompletableFuture,转换它们,并组合多个CompletableFuture。现在让我们弄明白当发生错误的时候我们应该怎么做。
首先让我们明白在一个回调链中错误是怎么传递的。思考下以下回调链:
CompletableFuture.supplyAsync(() -> {
// Code which might throw an exception
return "Some result";
}).thenApply(result -> {
return "processed result";
}).thenApply(result -> {
return "result after further processing";
}).thenAccept(result -> {
// do something with the final result
});
如果在原始的supplyAsync()
任务中发生一个错误,这时候没有任何thenApply
会被调用并且future将以一个异常结束。如果在第一个thenApply
发生错误,这时候第二个和第三个将不会被调用,同样的,future将以异常结束。
1. 使用 exceptionally() 回调处理异常 exceptionally()
回调给你一个从原始Future中生成的错误恢复的机会。你可以在这里记录这个异常并返回一个默认值。
Integer age = -1;
CompletableFuture<String> maturityFuture = CompletableFuture.supplyAsync(() -> {
if(age < 0) {
throw new IllegalArgumentException("Age can not be negative");
}
if(age > 18) {
return "Adult";
} else {
return "Child";
}
}).exceptionally(ex -> {
System.out.println("Oops! We have an exception - " + ex.getMessage());
return "Unknown!";
});
System.out.println("Maturity : " + maturityFuture.get());
2. 使用 handle() 方法处理异常 API提供了一个更通用的方法 - handle()
从异常恢复,无论一个异常是否发生它都会被调用。
Integer age = -1;
CompletableFuture<String> maturityFuture = CompletableFuture.supplyAsync(() -> {
if(age < 0) {
throw new IllegalArgumentException("Age can not be negative");
}
if(age > 18) {
return "Adult";
} else {
return "Child";
}
}).handle((res, ex) -> {
if(ex != null) {
System.out.println("Oops! We have an exception - " + ex.getMessage());
return "Unknown!";
}
return res;
});
System.out.println("Maturity : " + maturityFuture.get());
如果异常发生,res
参数将是 null,否则,ex
将是 null。