java常用异步调用
JAVA8之前的异步编程
- 继承Thead类,重写run方法
- 实现runable接口,实现run方法
- 匿名内部类编写thread或者实现runable的类,当然在java8中可以用lambda表达式简化
- 使用futureTask进行附带返回值的异步编程
- 使用线程池和Future来实现异步编程
- spring框架下的
@async
获得异步编程支持
下面本文尝试将Java异步调用的多种方式进行归纳。
一、通过创建新线程
首先的我们得认识到,**异步调用的本质,其实是通过开启一个新的线程来执行。**如以下例子:
public static void main(String[] args) throws Exception{
System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
new Thread(() -> {
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
}).start();
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis());
}
以上是采用Runable实现多线程创建方式的lambda写法,常见的多线程的实现方式有:1)继承Thread类,重写run()方法。2)实现runnable接口,重写run()方法。3)实现callable和future接口。
主线程 =====> 开始 =====> 1627893837146
异步线程 =====> 开始 =====> 1627893837200
主线程 =====> 结束 =====> 1627893839205
异步线程 =====> 结束 =====> 1627893842212
二、通过线程池
2.1 普通线程池
因为异步任务的实现本质的由新线程来执行任务,所以通过线程池的也可以实现异步执行。写法同我们利用线程池开启多线程一样。但由于我们的目的不是执行多线程,而是异步执行任务,所以一般需要另外一个线程就够了。
因此区别于执行多线程任务的我们常用的newFixedThreadPool,在执行异步任务时,我们用newSingleThreadExecutor 来创建一个单个线程的线程池。
public static void main(String[] args) throws Exception{
System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(()->{
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
});
executorService.shutdown(); // 回收线程池
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis());
}
可以看到,结果跟第一种结果是基本一致的。温馨提示:不要忘记线程池的回收
主线程 =====> 开始 =====> 1627895467578
异步线程 =====> 开始 =====> 1627895467635
主线程 =====> 结束 =====> 1627895469644
异步线程 =====> 结束 =====> 1627895472649
2.2 线程池与future实现异步编程
//构造线程池
ExecutorService pool = Executors.newCachedThreadPool();
try {
//构造future结果,doSomethingA十分耗时,因此采用异步
Future<Integer> future = pool.submit(() -> doSomethingA());
//做一些别的事情
doSomethingB();
//从future中获得结果,设置超时时间,超过了就抛异常
Integer result = future.get(10, TimeUnit.SECONDS);
//打印结果
System.out.printf("the async result is : %d", result);
//异常捕获
} catch (InterruptedException e) {
System.out.println("任务计算抛出了一个异常!");
} catch (ExecutionException e) {
System.out.println("线程在等待的过程中被中断了!");
} catch (TimeoutException e) {
System.out.println("future对象等待时间超时了!");
}
}
主线程 =====> 开始 =====> 1627895467578
异步线程 =====> 开始 =====> 1627895467635
主线程 =====> 结束 =====> 1627895469644
异步线程 =====> 结束 =====> 1627895472649
三、通过@Async注解
我们都知道,SpringBoot项目有一个的很重要的特点就是的注解化。如果你的项目是SpringBoot,那就又多了一种选择——@Async注解。
使用起来也非常简单,将要异步执行的代码封装成一个方法,然后用@Async注解该方法,然后在主方法中直接调用就行。
有以下两点需要注意:
- 类似
@Tranctional
注解,@Async
注解的方法与调用方法不能在同一个类中,否则不生效 - JUnit框架的设计不考虑多线程场景,所以主线程退出后,子线程也会跟着立即退出,所以可以在后面加多线程休眠时间来观察异步线程的执行情况
@Test
public void mainThread() throws Exception{
System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
collectionBill.asyncThread();
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis());
Thread.sleep(4000); // 用于防止jvm停止,导致异步线程中断
}
@Async
public void asyncThread(){
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
}
结果如上
四、通过CompletableFuture
CompletableFuture是JDK1.8的新特性,是对Future的扩展。如上2.2的future方式的异步编程仅仅能满足基本的需要,稍微复杂的一些异步处理Future接口似乎就有点束手无策了,而CompletableFuture实现了CompletionStage接口和Future接口,增加了异步回调、流式处理、多个Future组合处理的能力。
public static void main(String[] args) throws Exception{
System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
ExecutorService executorService = Executors.newSingleThreadExecutor();
CompletableFuture.runAsync(() ->{
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
},executorService);
executorService.shutdown(); // 回收线程池
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis());
}
4.1 使用构造函数创建CompletableFuture
public static class test{
public static String getTestResult()
{
int i = 10/0;
return "test";
}
}
public static void main(String[] args) {
CompletableFuture<String> completableFuture = new CompletableFuture();
new Thread(()->{
try {
completableFuture.complete(test.getTestResult());
} catch (Exception e) {
System.out.println("get exception in side");
completableFuture.completeExceptionally(e);
}
}).start();
try {
String result = completableFuture.get();
System.out.println(result);
} catch (Exception e) {
System.out.println("get exception out side");
e.printStackTrace();
}
}
一般需要complete() 设置异步线程可能返回的值,以及completeExceptionally() 向上抛出异常
1)客户端可以使用重载版本的get方法,它使用一个超时参数来避免发生这样的情况.future.get(10, TimeUnit.SECONDS)
2)使用 CompletableFuture的completeExceptionally方法将导致CompletableFuture内发生问 题的异常抛出
4.2 使用工厂方法创建CompletableFuture
CompletableFuture提供了四个静态方法用来创建CompletableFuture对象
//异步操作无返回值
public static CompletableFuture<Void> runAsync(Runnable runnable)
//使用指定线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
//异步操作有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
- Asynsc表示异步,而supplyAsync与runAsync不同在与前者异步返回一个结果,后者是void,这点通过看Runnable参数也是显而易见的
- 第二个函数第二个参数表示是用我们自己创建的线程池,否则采用默认的ForkJoinPool.commonPool()作为它的线程池
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{return "hello world";});
System.out.println(future.get()); //阻塞的获取结果 ''helllo world"
4.3 主动计算
对于异步的CompletableFuture有以下四个方法获取结果
public T get()
//设置超时时间
public T get(long timeout, TimeUnit unit)
//如果结果当前已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent值,不会阻塞
public T getNow(T valueIfAbsent)
//join()返回计算的结果或者抛出一个unchecked异常(CompletionException)
public T join()
join()返回计算的结果或者抛出一个unchecked异常(CompletionException)
4.4. 对多个异步任务进行流水线操作
java8 流式处理在CompletableFuture 也得到了完美的体现
4.4.1 计算结果完成时的处理
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的操作
//和之前的CompleteableFuture使用相同的线程
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
//新选线程
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
//指定线程池
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
//exceptionally方法返回一个新的CompletableFuture,当原始的CompletableFuture抛出异常的时候,就会触发这个CompletableFuture的计算
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
可以看到Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)
这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常
public class Main {
private static Random rand = new Random();
private static long t = System.currentTimeMillis();
static int getMoreData() {
System.out.println("begin to start compute");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("end to start compute. passed " + (System.currentTimeMillis() - t)/1000 + " seconds");
return rand.nextInt(1000);
}
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(Main::getMoreData);
Future<Integer> f = future.whenComplete((v, e) -> {
System.out.println(v);
System.out.println(e);
});
System.out.println(f.get());
System.in.read();
}
}
4.4.2 转换
如果我想转变计算结果的类型怎么办?
下面一组方法虽然也返回CompletableFuture对象,但是对象的值和原来的CompletableFuture计算的值不同,它的功能相当于将CompletableFuture转换成CompletableFuture
public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
可以看到handle会传递异常,也有下面的操作忽略对异常的处理
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<String> f = future.thenApplyAsync(i -> i * 10).thenApply(i -> i.toString());
System.out.println(f.get()); //"1000"
它们与handle方法的区别在于handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。而thenApply方法只是用来处理正常值,因此一旦有异常就会抛出
4.4.3 纯消费
上面的方法是当计算完成的时候,会生成新的计算结果(thenApply, handle),或者返回同样的计算结果whenComplete
CompletableFuture还提供了一种处理结果的方法,只对结果执行Action,而不返回新的计算值,因此计算值为Void
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
看它的参数类型也显而易见,它们是函数式接口Consumer,这个接口只有输入,没有返回值
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<Void> f = future.thenAccept(System.out::println);
System.out.println(f.get());
4.4.4 辅助方法 allOf 和 anyOf
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
allOf方法是当所有的CompletableFuture都执行完后执行计算。
anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。
他们接受数组类型的参数
CompletableFuture [] futures = {CompletableFuture.supplyAsync(()->randomDelay()),CompletableFuture.supplyAsync(()->randomDelay()),CompletableFuture.supplyAsync(()->randomDelay())};
CompletableFuture.allOf(futures).join();
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)