场景一是同步,场景二是异步,先拿到订单这个返回结果,订单其实就是一个future,即使它现在不能吃,但将来可以得到我们想要的结果
futureData构造速度快,是一个虚拟的数据,让调用者可以不用等待真实数据realData的生成,先去执行别的业务逻辑,当然如果这时候调用者要getRealData了,那么肯定得等待真实数据realData完全生成,因此可能会阻塞
package syn.pojo;
/**
* Client
*
* @author
* @date 2022/7/15 10:38
*/
public class Client {
// 模拟请求路径
public Data request(final String queryParam) {
// 定义先返回的一个对象
final FutureData futureData = new FutureData();
// 真实需要的对象,因为生成很慢,因此在单独的线程里进行
new Thread() {
@Override
public void run() {
RealData realData = null;
realData = new RealData(queryParam);
// 注意,这个方法执行的最后会唤醒等待在这个future上的对象
futureData.setRealData(realData);
}
}.start();
// futureData会立即返回,不会等待realData被构造完
return futureData;
}
// 调用入口
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
Data data = client.request("param");
System.out.println("请求完成");
// 假设这里还有其他业务逻辑要处理
Thread.sleep(1000);
// 获取真实的数据,如果数据还没准备好,会等待,再返回
System.out.println("数据=" + data.getResult());
}
}
public interface Data {
/**
* 数据的顶层接口
* @return
*/
String getResult() throws InterruptedException;
}
package syn.pojo;
/**
* FutureData
* 先返回的data数据,类似一个订单
*
* @author
* @date 2022/7/15 10:20
*/
public class FutureData implements Data {
/**
* 真实需要的数据
*/
protected RealData realData = null;
protected boolean isReady = false;
public synchronized void setRealData(RealData realData) {
// 如果数据已经生成过了,直接返回
if (isReady) {
return;
}
this.realData = realData;
// 数据已准备好
isReady = true;
// 唤醒在没有生成数据时获取数据的线程
notifyAll();
}
@Override
public String getResult() throws InterruptedException {
// 如果setRealData没有把数据准备好,则等待阻塞,注意会释放锁
while (!isReady) {
wait();
}
// 如果准备好则直接返回数据
return realData.result;
}
}
package syn.pojo;
/**
* RealData
*
* @author
* @date 2022/7/15 10:21
*/
public class RealData implements Data {
protected String result;
public RealData(String para) {
StringBuffer sb = new StringBuffer("realData:");
// 假设这里执行得特别慢,需要2秒
result = sb.toString() + para;
}
@Override
public String getResult() {
return result;
}
}
CompletableFuture
常用方法
依赖关系:
-thenApply():把前面任务的执行结果交给后面的function,返回的是第二个的CompletableFutre
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int result = 100;
System.out.println("第一次运算:" + result);
return result;
}).thenApply(number -> {
int result = number * 3;
System.out.println("第二次运算:" + result);
return result;
});
-thenCompose():用来连接两个有依赖关系的任务,会返回一个新的CompletableFutre
and集合关系:
thenCombine():合并任务,有返回值
thenAcceptBoth():两个任务执行完之后,将结果交给thenAcceptBoth处理,无返回值
runAfterBoth():两个任务都执行完成之后,执行下一步操作(Runnable类型任务)
or聚合关系:
applyToEither():两个任务哪个执行快就使用哪一个结果,有返回值
acceptEither():两个任务哪个执行快就消费哪一个结果,无返回值
runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)
并行执行:
allOf():当所有给定的CompletableFuture完成时,返回一个新的CompletableFuture
anyOf():当任何一个给定的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)
前者是runnable接口类型为参数,没有返回值;后者是以supplier接口类型为参数,有返回值,当get()时有返回值且会阻塞。
这两个开启异步都可以指定线程池,如果没指定Executor的方法时,内部使用ForkJoinPool去作为它的线程池执行异步,这个线程池默认创建的线程数是CPU的核数。如果所有CompletableFuture共享一个线程池,那么以但有任务执行一些很慢得I/O操作,那么就会导致线程池中所有线程都会阻塞,进而造成线程饥饿,影响整个系统的性能,所以还是要根据不同的业务类型创建不同的线程池,避免互相干扰
获取结果:
join()和get()方法都是用来获取CompletableFuture异步之后的返回值。join方法返回的是uncheck异常(即编译阶段无法发现),不会强制开发者抛出。get方法是经过检查的异常,编译阶段能被发现,需要用户手动处理,抛出或者try catch
thenCompose():连接异步任务,把前面任务的结果交给下一个异步任务,当supplyAsync就创建多一条线程,结果由第二个任务返回
thenComposeAsync:会直接把后面的任务所有代码开一个任务,当然如果后面还有一个supplyAsync,那么其实就是总共三个任务,但是第三个任务用的线程有可能跟第一个的线程一样。如果是thenCompose,那就只是在supplyAsync后面开启一个线程任务,可能只有两个,具体分析
thenCombine():合并异步任务,把上个任务和这个任务一起执行,其实就是相当于分开写了两个CompletableFuture,然后分别supplyAsync。两个任务都执行完之后,再把这两个结果加工成一个新的结果,由BiFunction返回。就类似厨师做了个番茄炒蛋,然后服务员在蒸饭,这两个任务一起搞定之后上菜给顾客。类似的有thenAcceptBoth,另一个是runAfterBoth
thenAcceptBoth():得到前面两个任务的结果后,直接内部消化,没有返回值
runAfterBoth():不关心前面两个任务的结果,并没有返回值
thenApply():任务的后置处理,把前面异步任务的结果交给后面的function,相当于stream api里的map操作。其实就是把thenApply后面的代码块放到上一个任务的末尾,其实就是一个任务,因此CompetableFuture会把这两部分的代码封装成一个任务,交给一个线程去执行,因此其实用的是同一个线程。thenAccept、thenRun与它类似
thenApplyAsync():也是任务的后置处理,但使用这个的话就不是同一个任务了,要先执行第二个任务就必须把第一个任务执行完,并且把第一个任务的结果交给第二个任务
thenAccept():会接收前面任务的结果,又不会产生返回值
thenRun():既不接收前面任务的结果,又不会产生返回值
applyToEither():这个方法的作用是上个任务和这个任务一起运行,哪个先运行完成就把结果先交给function。类似的有acceptEither和runAfterEither
acceptEither():会得到最快执行完任务的结果,但是没有返回值
runAfterEither():既不关心最快执行完任务的结果,并且也没有返回值
exceptionally(),只要上面任何一段链式出现异常问题,都会进入到这个方法中。不仅能加在尾部,也可以在中间加,只要前面出问题就走exceptionally,比try-catch简洁好多。类似的有handle和whenComplete
handle(),无论前面程序的执行结果是正常还是异常都会接收到,然后handle都会返回一个结果,然后让后面的程序继续执行
whenComplete():跟handle有点像,但是他没有返回值
基本上每个方法都有三种重载,xxx(args)、xxxAsync(args)、xxx(args,executor)