携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
前言
对于Node开发者来说,非阻塞异步编程是他们引以为傲的地方。而在JDK8中,也引入了非阻塞异步编程的概念。所谓非阻塞异步编程,就是一种不需要等待返回结果的多线程的回调方法的封装。使用非阻塞异步编程,可以很大程度上解决高并发场景的各种问题,提高程序的运行效率。
为什么要使用非阻塞异步编程 在jdk8之前,我们使用java的多线程编程,一般是通过Runnable中的run方法进行的。这种方法有个明显的缺点:没有返回值。这时候,大家会使用Callable+Future的方式去实现,代码如下。
public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor(); Future stringFuture = executor.submit(new Callable() { @Override public String call() throws Exception { Thread.sleep(2000); return "async thread"; } }); Thread.sleep(1000); System.out.println("main thread"); System.out.println(stringFuture.get()); } 这无疑是对高并发访问的一种缓冲方法。这种方式有一个致命的缺点就是阻塞式调用,当调用了get方法之后,会有大量的时间耗费在等待返回值之中。
不管怎么看,这种做法貌似都不太妥当,至少在代码美观性上就看起来很蛋疼。而且某些场景无法使用,比如:
多个异步线程执行时间可能不一致,我们的主线程不能一直等着。 两个异步任务之间相互独立,但是第二个依赖第一个的执行结果 在这种场景下,CompletableFuture的优势就展现出来了 。同时,CompletableFuture的封装中使用了函数式编程,这让我们的代码显得更加简洁、优雅。
不了解函数式编程的朋友,可以参考我之前的博客。JDK8新特性
CompletableFuture使用详解 runAsync和supplyAsync方法 CompletableFuture提供了四个静态方法来创建一个异步操作。
public static CompletableFuture runAsync(Runnable runnable) public static CompletableFuture runAsync(Runnable runnable, Executor executor) public static CompletableFuture supplyAsync(Supplier supplier) public static CompletableFuture supplyAsync(Supplier supplier, Executor executor) 没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。
runAsync方法不支持返回值。 supplyAsync可以支持返回值。 代码示例
/**
* 无返回值
*
* @throws Exception
*/
@Test
public void testRunAsync() throws Exception {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception ignored) {
}
System.out.println("run end ...");
});
future.get();
}
/**
* 有返回值
*
* @throws Exception
*/
@Test
public void testSupplyAsync() throws Exception {
CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
System.out.println("run end...");
return System.currentTimeMillis();
});
Long time = future.get();
System.out.println(time);
}
计算结果完成时的回调方法 当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的操作。
public CompletableFuture whenComplete(BiConsumer<? super T,? super Throwable> action) public CompletableFuture whenCompleteAsync(BiConsumer<? super T,? super Throwable> action) public CompletableFuture whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor) public CompletableFuture exceptionally(Function<Throwable,? extends T> fn) 这里需要说的一点是,whenComplete和whenCompleteAsync的区别。
whenComplete:使用执行当前任务的线程继续执行whenComplete的任务。 whenCompleteAsync:使用新的线程执行任务。 exceptionally:执行出现异常时,走这个方法。 代码示例
/**
* 当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。
* whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
* whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
* exceptionally:执行出现异常时,走这个方法
*
* @throws Exception
*/
@Test
public void testWhenComplete() throws Exception {
CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("运行结束");
}).whenComplete((t, action) -> {
System.out.println("执行完成");
}).exceptionally(t -> {
System.out.println("出现异常:" + t.getMessage());
return null;
});
TimeUnit.SECONDS.sleep(2);
}
thenApply 当一个线程依赖另一个线程时,可以使用thenApply方法把这两个线程串行化,第二个任务依赖第一个任务的返回值。
代码示例
/**
* 当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。
* 第二个任务依赖第一个任务的结果
*
* @throws Exception
*/
@Test
public void testThenApply() throws Exception {
CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
long result = new Random().nextInt();
System.out.println("result:" + result);
return result;
}).thenApply(t -> {
long result = t * 5;
System.out.println("result2:" + result);
return result;
});
Long result = future.get();
System.out.println(result);
}
以上就是本人对于Java异步编程的个人见解,今天的分享就到这,我们下期再见😀~~~///(^v^)~~~