前言
此刻的你正在上传自己的生活视频,但由于视频过大,于是开启一个新线程来进行上传,这样,你就可以处理其他事情,但是,你想查看上传完成没有,就不得不每隔几秒钟去检测。
public class CompletableFutureTest {
private static ExecutorService executor = Executors.newSingleThreadExecutor();
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = () -> {
int progress = 0;
while (progress <= 100) {
int i = new Random().nextInt(10);
progress += i;
System.out.println(progress + "%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "http://www.xxxx.mp4";
};
Future<?> submit = executor.submit(callable);
while (!submit.isDone()) {
// 没有完成,做其他事
}
System.out.println("上传完成" + submit.get());
}
}
后来你发现,这种做法非常麻烦、效率低下,于是开始探索更加方便的做法,最后你发现了CompletableFuture,CompletableFuture解决了Future不能解决的事,他可以异步回调,当任务完成后,会主动通知你,再也不需要你主动询问,从此,一切变的都轻而易举。
public class CompletableFutureTest {
public static void main(String[] args) {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
int progress = 0;
while (progress <= 100) {
int i = new Random().nextInt(10);
progress += i;
System.out.println(progress + "%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "http://www.xxxx.mp4";
});
completableFuture.thenAccept(s -> System.out.println("上传完成"+s));
while (!completableFuture.isDone()){
}
}
}
CompletableFuture 简介
那么到底什么是CompletableFuture呢?
首先要了解下异步编程,异步编程是一种在与主线程不同的线程上运行任务,并且他能够通知主线程进度、完成或失败,用它来编写非阻塞代码的方法。这样,主线程不会阻塞/等待任务完成,并且可以并行执行其他任务,拥有这种并行性极大地提高了程序的性能,所以,CompletableFuture就是用于Java中的异步编程。
CompletableFuture的出现,就是将以更优雅的方式解决异步问题。
supplyAsync、runAsync
使用 supplyAsync()方法构建CompletableFuture,它表示这个异步任务有返回值,如果没有返回值,可以使用runAsync()方法,就像下面这样:
CompletableFuture
.runAsync(() -> System.out.println(Thread.currentThread().getName()))
.thenAccept(unused -> System.out.println(Thread.currentThread().getName()+"完成"));
//输出
ForkJoinPool.commonPool-worker-1
main完成
既然有返回值,那么必定会有一个方法来获取,同Future一样,也是get()。
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "string");
System.out.println(completableFuture.get());
thenAccept
当在任务结束后,需要拿到他的返回值进行处理,在CompletableFuture中可以借助thenAccept,他的返回值还是CompletableFuture,多次调用,会依次被回调。
CompletableFuture
.runAsync(() -> System.out.println("处理任务"+Thread.currentThread().getName()))
.thenAccept(unused -> System.out.println("完成1"))
.thenAccept(unused -> System.out.println("完成2"))
.thenAccept(unused -> System.out.println("完成3"))
.thenAccept(unused -> System.out.println("完成4"));
thenAccept()可以访问CompletableFuture的结果,还有一个方法thenRun()无法访问,thenRun()是在异步任务执行后,以同步的方式调用Runnable。
thenAccept()方法也就是以同步的方式消费上一个异步任务的结果。
将两个 CompletableFuture组合在一起(链式执行)
当一个需求需要两个接口来完成时,我们可以将他们组合在一起,比如,要获取与用户A相同爱好的用户信息列表,首先需要从A服务器获取用户A的爱好,然后从B服务器获取相同爱好的用户,此时第二阶段需要第一阶段的值。
public class Move {
public static void main(String[] args) {
CompletableFuture<CompletableFuture<List<String>>> result = getUsersHobby("userId")
.thenApply(s -> getHobbyList(s));
try {
System.out.println(result.get().get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
static CompletableFuture<String> getUsersHobby(String userId) {
return CompletableFuture.supplyAsync(() -> {
//发起网络请求,获取userId的爱好
return "篮球";
});
}
static CompletableFuture<List<String>> getHobbyList(String hobby) {
//hobby是getUsersHobby()异步处理的结果
return CompletableFuture.supplyAsync(() -> {
//发起网络请求,获取具有爱好是hobby的所有用户
return Arrays.asList("张三", "李四");
});
}
}
使用thenApply() 方法导致了结果嵌套,我们不得不调用两次get来获取最终值,但如果希望最终结果是顶级 Future,那么可以改用thenCompose()方法
public static void main(String[] args) {
CompletableFuture<List<String>> result = getUsersHobby("userId")
.thenCompose(s -> getHobbyList(s));
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
thenApply方法也就是以同步的方式继续处理上一个异步任务的结果,当然还有thenApplyAsync()方法以异步方式处理上一个异步任务。
在两个CompletableFuture运行后再次计算
这种场景适用于当A和B通过异步取到值后,使用A、B计算出C,比如,获取两个用户的爱好交集,首先需要获取A用户的爱好,接着获取B用户的爱好,然后在求交集。
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<List<String>> aUser = CompletableFuture.supplyAsync(() -> {
return Stream.of("篮球","羽毛球").collect(Collectors.toList());
});
CompletableFuture<List<String>> bUser = CompletableFuture.supplyAsync(() -> {
return Stream.of("篮球","排球").collect(Collectors.toList());
});
CompletableFuture<List<String>> combinedFuture = aUser
.thenCombine(bUser, (aHobby, bHobby) -> {
aHobby.retainAll(bHobby);
return new ArrayList<>(aHobby);
});
System.out.println( combinedFuture.get());
}
还有一个方式是runAfterBoth(),他在两个任务完成后,分别以同步/异步的方式执行Runnable。
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,
Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
Runnable action)
将多个 CompletableFuture 组合在一起
CompletableFuture.allOf 适用于在执行完众多异步任务后做某事时使用,问题allOf()在于它返回CompletableFuture<Void>,表示没有结果集, 但是我们可以通过编写一些额外的代码行来获得所有包装的 CompletableFutures的结果
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture = CompletableFuture.allOf(getUserName("1"),
getUserName("2"),
getUserName("3")).thenAccept(new Consumer<Void>() {
@Override
public void accept(Void unused) {
System.out.println("完成");
}
});
completableFuture.join();
}
static CompletableFuture<String> getUserName(String userId) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "用户-" + userId;
});
}
如果想运行一批任务中的其中一个,可以使用anyOf()方法,他会在其中挑一个进行执行。
CompletableFuture 异常处理
程序运行避免不了错误,那么CompletableFuture是如何处理异常的呢,先看以下代码,如果在supplyAsync()任务中发生错误,那么就不会调用任何thenApply()的回调,如果在第一个thenApply()回调中发生错误,则不会调用第二个及以后的,依此类推。
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
});
使用exceptionly()回调处理异常
如果程序要捕获异步任务过程中遇到的错误,可以在exceptionally中处理,并且返回一个默认值。
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> maturityFuture = CompletableFuture.supplyAsync(() -> {
return 1/0;
}).exceptionally(ex -> {
ex.printStackTrace();
return 1;
});
System.out.println(maturityFuture.get());
}