Future接口用于定义异步任务有返回值的线程,接口里面定义了相应的规范,可以为主线程开一个分支任务,帮助主线程执行一个很耗时费力的任务,执行完毕后主线程可通过get方法获取结果。
异步任务有返回值的线程需要满足三个条件:
多线程
有返回值
异步的任务
实现Runnable接口后重写run方法是没有返回值的,以及不能抛出异常。而callable接口则恰好弥补了有返回值这个需求。
而且我们会发现RunnableFuture恰好继承了Runnable和Future,所以使用RunnableFuture就满足了多线程以及异步的任务这两个要求。
那么现在的痛点就是我们如何把RunnableFuture这个接口的功能和Callable接口的有返回值来结合起来呢,此时FutureTask类就诞生了。
如下图我们得知:FutureTask实现了RunnableFuture接口,并且通过构造方法的方式注入了Callable接口。
优点:
使用线程池+FutureTask能显著的提升程序运行的效率,比单线程执行多个任务的效率要快很多。
缺点:
get()方法容易阻塞,一旦调用get()方法获取结果,如果线程中计算没有完成则容易导致程序进行阻塞。可以设置一个最长的等待时间,如果延期了没有获取到,直接抛出异常(这种方式不推荐)。
isDone()的轮询访问,会耗费大量的CPU资源,也不能及时的获取到结果,但是可以打印显示出进度如何。
Future接口在结果的获取上不是很友好,只能通过阻塞或者轮询的方式得到任务的结果。
CompletableFuture提供了一种观察者模式类似的机制,可以让线程任务执行完成后通知监听的一方(回调机制)。
CompletableFuture<返回值类型>分为runAsync无返回值和supplyAsync有返回值两种。创建时如果没有指定线程池,则会使用默认的ForkJoinPool.commonPool线程池(会自动回收,相当于守护线程)。
CompletionStage代表异步计算的一个阶段,一个阶段完成之后可以进行到另一个阶段当中,使得功能更加的强大。
//异步调用,有返回值
CompletableFuture completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"completableFuture");
return 100;
}).whenComplete((t,u)->{ //相当于是前端的 .then((resp)=>{ });
System.out.println(t); //拿到返回值 100
System.out.println(u); //如果内部出现了异常,输出异常信息
});
System.out.println(completableFuture.get());//get方法外部拿到返回值
System.out.println(completableFuture.join());//join方法外部拿到返回值
引入CompletableFuture后,减少了Future中获取返回值时阻塞和轮询的这种低效率的方式,利用回调,当任务完成或者发生异常时,自动的调用回调方法。
注意:我们尽量要使用自己创建的线程池,因为这样的话我们可以手动的控制线程池的关闭回收的时间,保证在执行回调方法前避免程序结束。
如果使用get方法会出现检查异常,我们必须处理异常,或声明或try-catch。
如果使用join方法不会出现检查异常,运行时如果出错,程序会直接报错。
public class Test1 {
static List list = Arrays.asList(new NetMall("jd"),
new NetMall("taobao"),
new NetMall("dangdang"));
//一个个查询
public static List getPrice(List list, String productName) {
//《Mysql》 in jd price is 88.05
return list
.stream()
.map(netMall ->
String.format("《" + productName + "》" + "in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName)))
.collect(Collectors.toList());
}
//通过CompletableFuture异步线程来完成
public static List getPriceByCompletableFuture(List list, String productName) {
return list.stream().map(netMall ->
CompletableFuture.supplyAsync(() ->
String.format("《" + productName + "》" + "in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName)))) //Stream>
.collect(Collectors.toList()) //List>
.stream()//Stream
.map(s -> s.join()).collect(Collectors.toList()); //List
}
public static void main(String[] args) {
long StartTime = System.currentTimeMillis();
List list1 = getPrice(list, "mysql");
for (String element : list1) {
System.out.println(element);
}
long endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒");
System.out.println("------------------------------------------------------");
long StartTime2 = System.currentTimeMillis();
List list2 = getPriceByCompletableFuture(list, "mysql");
for (String element : list2) {
System.out.println(element);
}
long endTime2 = System.currentTimeMillis();
System.out.println("------costTime" + (endTime2 - StartTime2) + " 毫秒");
}
}
class NetMall {
private String netMallName;
public NetMall(String netMallName) {
this.netMallName = netMallName;
}
public String getNetMallName() {
return netMallName;
}
public void setNetMallName(String netMallName) {
this.netMallName = netMallName;
}
public double calcPrice(String productName) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0); //随机生成价格
}
}
两者运行的结果进行对比:
《mysql》in jd price is 109.42
《mysql》in taobao price is 109.85
《mysql》in dangdang price is 109.28
------costTime: 3092 毫秒 //一个个查询
------------------------------------------------------
《mysql》in jd price is 110.24
《mysql》in taobao price is 110.40
《mysql》in dangdang price is 109.10
------costTime1022 毫秒 //通过CompletableFuture异步线程
获取结果:
public T get()
public T get(long timeout,TimeUnit unit)
public T join() ---> 和get()方法功能一样,只是不需要处理检查异常
public T getNow(T valuelfAbsent) ---> 立即获取,如果没完成直接返回入参值,不会阻塞。
主动触发计算:
public boolean complete(T value) ---> 如果没有计算完成则直接打断计算,将value作为线程的返回值,get/join方法获取的就是value值。
thenApply ——> 计算的结果存在依赖的关系,下一步需要使用到上一步的计算结果,但是如果出现异常,那么就直接停止。
handle ——> 计算的结果也存在依赖的关系,优点是如果出现了异常也可以往下一步走。
thenApply(f -> {
System.out.println("222");
return f + 2;
})
handle((f, e) -> {
System.out.println("3333");
int i=10/0;
return f + 2;
})
接收到任务的处理结果,并消费,无返回结果。
对比补充:
thenRun(Runnable runnable):任务A执行完执行B,并且不需要A的结果,没有返回值。
thenAccept(Consumer action):任务A执行完执行B,B需要A的结果,但是任务B没有返回值。
thenApply(Function fn):任务A执行完执行B,B需要A的结果,同时任务B有返回值。
thenRun和thenRunAsync在线程池使用上的区别:
如果没有传入自定义线程池,都会默认使用线程池ForkJoinPool。
如果使用自定义线程池,使用thenRun方法时,第一个任务和后面的任务都共用一个自定义线程池。
使用thenRunAsync方法时,第一个任务使用的是自定义线程池,后面的使用的是默认的ForkJoinPool。
applyToEither谁快用谁
CompletableFuture result = CompletableFutureA.applyToEither(CompletableFutureB, f -> {
return f + " is winner"; //f就是胜利者的返回值
});
thenCombine将两个任务的结果进行合并,先完成的先等着,等待其他的分支任务。
CompletableFuture finalResult = completableFuture1.thenCombine(completableFuture2,(x, y)->{ //x,y是
System.out.println("----------开始两个结果合并");
return x + y;
});