CompletableFuture 异步编程
我们知道Java是一个支持多线程的语言,多线程模式的并发编程模型在Java中一直是主流,然而随着时代发展我们也认识到,其实异步编程模型在某些场景下更为适合,Java当然也可以实现异步编程,但始终缺乏一些语言或是库层面的支持,大量的异步操作容易出现「回调地狱」等情况。
直到JDK8中引入了一个CompletableFuture对象,它和JavaScript中的Promise有些类似,通过这个对象我们能较为简单的编写Java异步代码。
supplyAsync 创建异步任务
我们可以使用静态方法CompletableFuture.runAsync()和CompletableFuture.supplyAsync()创建异步任务。这里我们直接看一个例子。
package com.gacfox.demo;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建异步任务
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("异步任务执行完成");
});
// 主线程阻塞等待
future.get();
}
}
代码中,我们通过CompletableFuture.runAsync()创建了一个异步任务,然后调用future.get()让主线程阻塞等待异步任务返回。
CompletableFuture也能够获取返回值,我们使用supplyAsync()方法创建异步任务即可,下面是一个例子。
package com.gacfox.demo;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("异步任务执行完成");
return "success";
});
// 主线程阻塞等待
String s = future.get();
System.out.println("返回值:" + s);
}
}
thenApply 异步任务依赖
有时多个异步任务有依赖关系,比如我们先调用接口A获取数据,再根据A的返回值调用接口B获取数据,这两个异步任务就是有依赖的,需要按照先后顺序串行执行。如果不使用CompletableFuture我们就得写一个嵌套的异步调用,嵌套层数太多代码可读性就很低了。而CompletableFuture能够很好的解决这个问题,下面是一个例子。
package com.gacfox.demo;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("调用接口A");
return "aaa";
}).thenApply((s) -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("调用接口B");
return s + "bbb";
});
// 主线程阻塞等待
String s = future.get();
System.out.println("返回值:" + s);
}
}
上面代码中,会顺序执行两个异步任务,最终输出aaabbb。
除了thenApply()方法还有一个thenRun()方法,区别是没有上一步的传参,这里就不展开介绍了。
thenAccept 消费最终结果
前面代码我们都是在主线程中进行阻塞等待,其实这不太符合异步任务的实际使用场景,实际开发中,更多的时候我们需要异步任务完成后自己进行一些处理,而不需要主线程等着它完成。此时我们可以调用thenAccept方法实现。
package com.gacfox.demo;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
// 创建异步任务
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("执行异步任务");
return "aaa";
}).thenAccept((s) -> {
System.out.println("消费最终结果");
System.out.println("输出" + s);
});
}
}
上面代码中,我们的异步任务执行完后,会自动调用thenAccept()中的内容,主线程不会被阻塞了。
注:实际运行上述代码,其实你是看不到任何返回值的,因为还没等异步任务执行完主线程就结束了,这也侧面说明了主线程没有被阻塞。要想看到返回结果,我们借助让主线程睡眠等手段来实现,这里就不赘述了。
exceptionally 异常处理
我们可以调用exceptionally进行异常处理,下面是一个例子。
package com.gacfox.demo;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
// 创建异步任务
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
int i = 1 / 0;
return "aaa";
}).exceptionally((ex) -> {
System.out.println("发生了异常:" + ex.getMessage());
return "bbb";
});
}
}
这里由于我们手动触发了1/0的除零异常,因此整个执行流程继续交给了exceptionally()进行处理。