CompletableFuture

1.Future接口

Future接口用于定义异步任务有返回值的线程,接口里面定义了相应的规范,可以为主线程开一个分支任务,帮助主线程执行一个很耗时费力的任务,执行完毕后主线程可通过get方法获取结果。

2.FutureTask诞生的原因

异步任务有返回值的线程需要满足三个条件:

  1. 多线程

  2. 有返回值

  3. 异步的任务

实现Runnable接口后重写run方法是没有返回值的,以及不能抛出异常。而callable接口则恰好弥补了有返回值这个需求。

而且我们会发现RunnableFuture恰好继承了Runnable和Future,所以使用RunnableFuture就满足了多线程以及异步的任务这两个要求。

那么现在的痛点就是我们如何把RunnableFuture这个接口的功能和Callable接口的有返回值来结合起来呢,此时FutureTask类就诞生了。

如下图我们得知:FutureTask实现了RunnableFuture接口,并且通过构造方法的方式注入了Callable接口。

CompletableFuture_第1张图片

CompletableFuture_第2张图片

3.使用Future的优缺点

优点:

  • 使用线程池+FutureTask能显著的提升程序运行的效率,比单线程执行多个任务的效率要快很多。

缺点:

  • get()方法容易阻塞,一旦调用get()方法获取结果,如果线程中计算没有完成则容易导致程序进行阻塞。可以设置一个最长的等待时间,如果延期了没有获取到,直接抛出异常(这种方式不推荐)。

  • isDone()的轮询访问,会耗费大量的CPU资源,也不能及时的获取到结果,但是可以打印显示出进度如何。

Future接口在结果的获取上不是很友好,只能通过阻塞或者轮询的方式得到任务的结果。 

4.从Future到CompletableFuture

CompletableFuture提供了一种观察者模式类似的机制,可以让线程任务执行完成后通知监听的一方(回调机制)。

CompletableFuture_第3张图片

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中获取返回值时阻塞和轮询的这种低效率的方式,利用回调,当任务完成或者发生异常时,自动的调用回调方法。

注意:我们尽量要使用自己创建的线程池,因为这样的话我们可以手动的控制线程池的关闭回收的时间,保证在执行回调方法前避免程序结束。

4.1get方法和join方法的区别

如果使用get方法会出现检查异常,我们必须处理异常,或声明或try-catch。

如果使用join方法不会出现检查异常,运行时如果出错,程序会直接报错。

5.CompletableFuture实际使用案例

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异步线程

6.Completable常用方法介绍

6.1获得结果和触发计算

获取结果

  • 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值。

6.2对计算结果进行处理

  • 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;
})

 

6.3对计算结果进行消费

接收到任务的处理结果,并消费,无返回结果。

对比补充:

  • 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。

6.4对计算速度进行选用

  • applyToEither谁快用谁

CompletableFuture result = CompletableFutureA.applyToEither(CompletableFutureB, f -> {
	return f + " is winner"; //f就是胜利者的返回值
});

6.5对计算结果进行合并

  • thenCombine将两个任务的结果进行合并,先完成的先等着,等待其他的分支任务。

CompletableFuture finalResult = completableFuture1.thenCombine(completableFuture2,(x, y)->{  //x,y是
    System.out.println("----------开始两个结果合并");
    return x + y;
});

 

你可能感兴趣的:(并发编程,java,开发语言)