异步回调--CompletableFuture异步编排
部分内容直接来自:tangzhi.blog.csdn.net/article/det…
1、同步和异步
同步
在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
而我们平时经常讨论的同步问题多发生在多线程环境中的数据共享问题。即当多个线程需要访问同一个资源时,它们需要以某种顺序来确保该资源在某一特定时刻只能被一个线程所访问,如果使用异步,程序的运行结果将不可预料。因此,在这种情况下,就必须对数据进行同步,即限制只能有一个进程访问资源,其他线程必须等待。
实现同步的机制主要有临界区、互斥、信号量和事件【参考操作系统】
异步
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
举例说明
异步:在日常生活中,我们要烧水,可以在烧水的时候煮饭做菜。等水开了,再去灌水,这就是异步。定时器(setTimeOut) ,网络请求(ajax),事件监听(addEventListener) 都是异步的。ajax虽然可以改写成同步的,但禁止使用.
同步:必须完成这件事,才能做下一件事,这件事做不完,就做不了下一件事。比如,你还没写完作业,而恰巧你的小伙伴来找你完,你的父母就和你说,写完作业才能出去玩。那能怎么办呢,只能先写完作业,才能出去玩。
还有一个例子,就是打游戏通关,这个游戏通过了第一关才能打第二关,这就是同步。
2、回调
软件模块之间总是存在一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。
同步调用
同步调用是最基本并且最简单的一种调用方式,类A的方法a()调用类B的方法b(),一直等待b()方法执行完毕,a()方法再继续往下走。这种调用方式适用于方法b()执行时间不长的情况,因为b()方法执行时间一长或者直接阻塞的话,a()方法的余下代码是无法执行下去的,这样会造成整个流程的阻塞。
异步调用
是一种类似消息或事件的机制,是为了解决同步调用可能出现阻塞,导致整个流程卡住而产生的一种调用方式。类A的方法a()通过新起线程的方式调用类B的方法b(), 代码接着直接往下执行,这样无论方法b()执行时间多久,都不会阻塞方法a()的执行。但是这种方式,由于方法a()不等待方法b()执行完成,在方法a()需要方法b()执行结果的情况下(视具体业务而定,有些业务比如启动异步线程发个微信通知、刷新一个缓存这种就没有必要),必须通过一定的方法对方法b()的执行结果进行监听。在Java中,可以使用Future+Callable的方式做到这一点。
回调
回调的思想是:
- 类A的a()方法调用了类B的b()方法
- 类B的b方法执行完毕主动调用类A的
callback()方法
这样一种调用方式组成了上图,也就是一种双向的调用方式,回调函数是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类称为回调类,回调类的对象称为回调对象。
回调的核心就是回调方将本身即this传递给调用方,这样调用方就可以在调用完毕之后告诉回调方它想要知道的信息。
3、CompletableFuture概述
在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
它可能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作
它实现了Future和CompletionStage接口
CompletionStage接口说明
- CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成后可能会触发另外一个阶段
- 一个阶段的计算执行可以是一个Funcation、Consumer、Runnable。比如
:stage.thenApply (x->square(x)).thenAccept(x->System.out.println(x)).thenRun(()->{System.out.println()}); - 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发,有些类似Linux系统的管道分隔符传参数
4、CompletableFuture创建方式
CompletableFuture 提供了四个静态方法来创建一个异步操作
//runAsync方法不支持返回值
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
//supplyAsync可以支持返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
其中:
- runAsync方法不支持返回值
- supplyAsync可以支持返回值
4.1、异步调用,无返回值
实例1:
runAsync方法不支持返回值
public static void main(String[] args)throws Exception{
CompletableFuture future1=CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName()+"*********future1 coming in");
});
System.out.println("1111");
//这里获取到的值是null
System.out.println(future1.get());
}
结果:主线程往下走,可以中途获取返回值,回调。
1111
ForkJoinPool.commonPool-worker-1*********future1 coming in
null
实例2:
public static void main(String[] args) throws Exception{
ThreadPoolExecutor executor = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3));
//(2). CompletableFuture.runAsync(Runnable runnable,Executor executor);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
//ForkJoinPool.commonPool-worker-9
System.out.println(Thread.currentThread().getName() + "\t" + "*********future2 coming in");
}, executor);
}
结果:
pool-1-thread-1 *********future2 coming in
总结:没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同
4.2、异步调用,有返回值
实例1:
public static Integer supplyAsync() throws Exception {
//有返回值的异步调用
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"有返回值的异步回调");
//int i=1/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t->" + t); //正常的返回结果
System.out.println("u->" + u); //错误信息
}).exceptionally((e) -> {
e.printStackTrace();
return 500;
}).get());
return null;
}
结果:
ForkJoinPool.commonPool-worker-1有返回值的异步回调
t->1024
u->null
1024
有异常的错误执行:
ForkJoinPool.commonPool-worker-1有返回值的异步回调
t->null
u->java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
500
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1606)
at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1067)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1703)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:172)
Caused by: java.lang.ArithmeticException: / by zero
at juc.异步回调.Test.lambda$supplyAsync$1(Test.java:35)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
... 5 more
whenComplete: 有两个参数,一个是t 一个是u
- T:是代表的 正常返回的结果;
- U:是代表的 抛出异常的错误信息;
- 如果发生了异常,get可以获取到exceptionally返回的值;
实例2:
public static void main(String[] args) throws Exception{
ThreadPoolExecutor executor = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3));
//(4).public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
CompletableFuture<Integer> future4 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "future4带有返回值");
return 1025;
}, executor);
System.out.println(future4.get());//使用whencomplet
//关闭线程池
executor.shutdown();
}
结果
pool-1-thread-1 future4带有返回值
1025
5、CompletableFuture 的API
5.1、获得结果和触发计算(get、getNow、join、complete)
演示:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
});
//(1).public T get()不见不散(会抛出异常)
//System.out.println(future.get());
//(2).public T get(long timeout, TimeUnit unit) 过时不候2s后如果没有返回结果就报错
//System.out.println(future.get(2,TimeUnit.SECONDS));
//public T getNow(T valuelfAbsent)
try { TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//没有计算完成的情况下,给我一个替代结果
//Integer now = future.getNow(3);
//这里停顿了3s,而我2s后就有结果了,所以可以正常拿到值 false获取到的值是1
//如果这里停顿1s,而我2s后才有结果,那么就不可以正常拿到值,true获取到的值是444
boolean flag = future.complete(444);
System.out.println(flag+"获取到的值是"+future.get());
5.2、对计算结果进行处理(thenApply、handle)
- whenComplete:是执行当前任务的线程执行继续执行whenComplete的任务
- whenCompleteAsync:是执行把whenCompleteAsync这个任务继续提交给线程池来进行执行
演示:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {e.printStackTrace();}
return 1;
}).thenApply(s->{
System.out.println("-----1");
//如果加上int error=1/0; 由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
//int error=1/0;
return s+1;
}).thenApply(s->{
System.out.println("-----2");
return s+2;
}).whenComplete((v,e)->{ //v是返回值,e是获取到的异常信息,没有异常就为null
if(e==null){
System.out.println("result-----"+v);
}
}).exceptionally(e->{
e.printStackTrace();
return null;
});
System.out.println(Thread.currentThread().getName()+"\t"+"over....");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
5.3、对计算结果进行消费(thenAccept、thenRun)
③. 对计算结果进行消费
CompletableFuture.supplyAsync(() -> {
return 1;
}).thenApply(f -> {
return f+2;
}).thenApply(f -> {
return f+3;
}).thenAccept(r -> System.out.println(r));
// 任务A执行完执行B,并且B不需要A的结果
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {}).join());
// 任务A执行完成执行B,B需要A的结果,但是任务B无返回值
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {}).join());
// 任务A执行完成执行B,B需要A的结果,同时任务B有返回值
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB").join());
5.4、对计算速度进行选用(applyToEither)
//这个方法表示的是,谁快就用谁的结果,类似于我们在打跑得快,或者麻将谁赢了就返回给谁
//public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn);
//下面这个在第一个中停留1s,在第二种停留2s,返回的结果是1
System.out.println(CompletableFuture.supplyAsync(() -> {
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {e.printStackTrace();}
return 1;
}).applyToEither(CompletableFuture.supplyAsync(() -> {
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {e.printStackTrace();}
return 2;
}), r -> {
return r;
}).join());
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
5.5、对计算结果进行合并(thenCombine)
//public <U,V> CompletableFuture<V> thenCombine
//(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
//两个CompletionStage任务都完成后,最终把两个任务的结果一起交给thenCombine来处理
//先完成的先等着,等待其他分支任务
System.out.println(CompletableFuture.supplyAsync(() -> {
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
return 20;
}), (r1, r2) -> {
return r1 + r2;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
return 30;
}), (r3, r4) -> {
return r3 + r4;
}).join());
System.out.println(CompletableFuture.supplyAsync(() -> {
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
return 20;
}), (r1, r2) -> {
return r1 + r2;
}).join());
对计算结果进行合并
//public <U,V> CompletableFuture<V> thenCombine
//(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
//两个CompletionStage任务都完成后,最终把两个任务的结果一起交给thenCombine来处理
//先完成的先等着,等待其他分支任务
System.out.println(CompletableFuture.supplyAsync(() -> {
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
return 20;
}), (r1, r2) -> {
return r1 + r2;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
return 30;
}), (r3, r4) -> {
return r3 + r4;
}).join());
System.out.println(CompletableFuture.supplyAsync(() -> {
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
return 20;
}), (r1, r2) -> {
return r1 + r2;
}).join());
6、Future模式
普通模式与Future模式的简单对比:
-
普通模式在处理多任务时是串行的,在遇到耗时操作的时候只能等待,直到阻塞被解除,才会继续执行下一个任务
-
Future模式,只是发起了耗时操作,函数立马就返回了,真正执行具体操作由另外一个工作线程去完成,并不会阻塞客户端线程。所以在工作线程执行耗时操作的时候客户端无需等待,可以继续做其他事情,等到需要的时候再向工作线程获取结果。
Future模式详解:
是多线程设计常用的一种设计模式。它的核心思想是异步调用。对于Future模式来说,它无法立即返回你需要的数据,但是它会返回一个契约,将来你可以凭借这个契约去获取你需要的信息。
Future模式可以简单理解成:我有一个任务,它比较耗时,但是我又不想一直空等,而且有时候任务的结果并不立刻需要,于是我把这任务提交给了Future,Future替我完成这个任务,同时Future将这个任务订单的信息返回给我。那么我就可以不用等了,自己可以去做任何想做的事情。当我需要这个任务结果的时候,我可以根据返回的订单信息,尝试从Future那里去取出该任务的结果(当然如果此时还未完成,则会阻塞)。当然,考虑到此时任务可能还未完成,Future也支持任务是否完成检测,由此,我们可以根据是否完成设计不同的应当逻辑。
为了更好理解future模式的优势,可以自己编写一些演示代码,网上也有很多示例。
理解了future模式之后,剩下就需要关注其主要实现了。