CompletableFuture

Future和Callable接口

  • Future接口(实现类FutureTask)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。

  • Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。

  • 比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,过了一会才去获取子任务的执行结果。

  • 异步多线程任务执行且有返回结果,三个特点: 多线程/有返回/异步任务

FutureTask实现类

在这里插入图片描述
在这里插入图片描述

他既继承了RunnableFuture接口,也在构造方法中实现了Callable接口(有返回值、可抛出异常)和Runnable接口。
因此FutureTask 满足了三个条件:多线程/有返回/异步

Future到CompletableFuture

Future优点

  • future+线程池异步多线程任务配合,能显著提高程序的执行效率。

Future缺点

  • get()阻塞:一旦调用get()方法,不管是否计算完成,都会导致阻塞(所以一般get方法放到最后)
  • isDone()轮询:利用while(true) 中嵌套if(futureTask.isDone())的方式使得他在结束之后才get(),但是也会消耗cpu

CompletableFuture

阻塞的方式和异步编程的设计理念相违背,而轮询的方式会消耗的CPU资源。因此,JDK1.8设计出CompletableFuture

在这里插入图片描述

  • 在Java 1.8中, Complet ableFuture提供了非常强大的Future的扩展功能, 可以帮助我们简化异步编程的复杂性, 并且提供了函数式编程的能力, 可以通过回调的方式处理计算结果, 也提供了转换和组合CompletableFuture的方法。

  • 它可能代表一个明确完成的Future, 也有可能代表一个完成阶段(CompletionStage) , 它支持在计算完成以后触发一些函数或执行某些
    动作。

  • 它实现了Future和CompletionStage接口

CompletionStage

  • CompletionStage代表异步计算过程中的某一个阶段, 一个阶段完成以后可能会触发另外一个阶段

  • 一个阶段的计算执行可以是一个Function, Consumer或者Runnable。一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。

核心的四个静态方法

  • 利用核心的四个静态方法创建一个异步操作 (不建议用new)

  • 关键就是 有没有返回值/是否用了线程池

  1. runAsync无返回值 + runAsync和线程池
  2. supplyAsync有返回值 + supplyAsync和线程池

减少阻塞和轮询whenComplete

public class CompletableFutureUseDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Object> objectCompletableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"----副线程come in");
            int result = ThreadLocalRandom.current().nextInt(10);//产生一个随机数
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("1秒钟后出结果"+result);
            return result;
        });

        System.out.println(Thread.currentThread().getName()+"线程先去忙其他任务");
        System.out.println(objectCompletableFuture.get());
    }
}
//main线程先去忙其他任务
//ForkJoinPool.commonPool-worker-9----副线程come in
//1秒钟后出结果6
//6

CompletableFuture优点总结

  • 异步任务结束时,会自动回调某个对象的方法。

  • 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行。

  • 异步任务出错时,会自动回调某个对象的方法。

使用案例

需求说明
1 同一款产品,同时搜索出同款产品在各大电商平台的售价;
2 输出返回:
出来结果希望是同款产品的在不同地方的价格清单列表, 返回一个List<String>
《mysql》in jd price is 88.05
《mysql》in dang dang price is 86.11
《mysql》in tao bao price is 90.43
3 解决方案,比对同一个商品在各个平台上的价格,要求获得一个清单列表
1   stepbystep   , 按部就班, 查完京东查淘宝, 查完淘宝查天猫......
2   all in       ,万箭齐发,一口气多线程异步任务同时查询
  • step by step
public class Case {
    static List<NetMall> list = Arrays.asList(
      new NetMall("jd"),
      new NetMall("dangdang"),
        new NetMall("taobao")
    );

    public static List<String> getPrice(List<NetMall> list,String productName){
        return list
                .stream() //----流式计算做了映射(利用map),希望出来的是有格式的字符串(利用String.format),%是占位符
                .map(netMall -> String.format(productName + " in %s price is %.2f",
                                netMall.getNetMallName(),//第一个%
                                netMall.calcPrice(productName))).collect(Collectors.toList());//第二个%
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        List<String> list1 = getPrice(list, "mysql");
        for(String element:list1){
            System.out.println(element);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("---当前操作花费时间----costTime:"+(endTime-startTime)+"毫秒");
    }
}

class NetMall{
    @Getter
    private String netMallName;

    public NetMall(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 110.48
//mysql in dangdang price is 109.06
//mysql in taobao price is 110.96
//---当前操作花费时间----costTime:3098毫秒
  • all in
public class Case {
    static List<NetMall> list = Arrays.asList(
      new NetMall("jd"),
      new NetMall("dangdang"),
        new NetMall("taobao")
    );

    public static List<String> getPrice(List<NetMall> list,String productName){
        return list
                .stream() //----流式计算做了映射(利用map),希望出来的是有格式的字符串(利用String.format),%是占位符
                .map(netMall -> String.format(productName + " in %s price is %.2f",
                                netMall.getNetMallName(),//第一个%
                                netMall.calcPrice(productName))).collect(Collectors.toList());//第二个%
    }

    //从功能到性能
    public static List<String> getPricesByCompletableFuture(List<NetMall> list,String productName){
        return list.stream().map(netMall ->
                        CompletableFuture.supplyAsync(() -> String.format(productName + " in %s price is %.2f",
                                netMall.getNetMallName(),
                                netMall.calcPrice(productName))))//Stream<CompletableFuture<String>>
                                .collect(Collectors.toList())//List<CompletablFuture<String>>
                                .stream()//Stream<CompletableFuture<String>
                                .map(s->s.join())//Stream<String>
                                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        startTime = System.currentTimeMillis();
        List<String> list = getPricesByCompletableFuture(list, "mysql");
        for(String element:list){
            System.out.println(element);
        }
        endTime = System.currentTimeMillis();
        System.out.println("--性能版-当前操作花费时间----costTime:"+(endTime-startTime)+"毫秒");
    }
}

class NetMall{
    @Getter
    private String netMallName;

    public NetMall(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.34
//mysql in dangdang price is 109.02
//mysql in taobao price is 110.37
//--性能版-当前操作花费时间----costTime:1000毫秒

随着数据量的增大,二者的差距越来越多。

CompletableFuture常用API

获得结果和触发计算
  • 获取结果

    • public T get() 不见不散,容易阻塞

    • public T get(long timeout,TimeUnit unit) 过时不候,超过时间会爆异常

    • public T join() 类似于get(),区别在于是否需要抛出异常

    • public T getNow(T valueIfAbsent),没有计算完成的情况下,给一个替代结果,立即获取结果不阻塞,计算完,返回计算完成后的结果,没算完,返回设定的valueAbsent(直接返回了备胎值xxx)

  • 主动触发计算

    • public boolean complete(T value) 是否立即打断get()方法返回括号值,(执行要2s,等待只有1s,所以还没执行完就被打断了。返回true表示打断了获取这个过程,直接返回了备胎值complete;如果没打断,返回false 和计算的结果)
对计算结果进行处理
  • thenApply() 计算结果存在在依赖关系,使得线程串行化。因为依赖关系,所以一旦有异常,直接叫停。
  • handle () 类似于thenApply,但是有异常的话仍然可以往下走一步。
对计算结果进行消费
  • thenAccept() 接收任务的处理结果,并消费处理,消费型函数式接口

thenRun,thenAccept,thenApply的区别
thenRun(Runnable runnable)
任务A执行完执行B,并且B不需要A的结果
thenAccept(Consumer action)
任务A执行完执行B,B需要A的结果,但是任务B无返回值
thenApply(Function fn)
任务A执行完执行B,B需要A的结果,同时任务B有返回值

CompleteFuture和线程池
  • 上面的几个方法都有普通版本和后面加Async的版本
  • 没有传入自定义线程池,都用默认线程池ForkJoinPool
  • 传入了一个自定义线程池如果你执行第一个任务的时候,传入了一个自定义线程池
  • 调用thenRun()执行第二个任务的时候,则第二个任务和第一个任务是用同一个线程池
  • 调用thenRunAsync()执行第二个任务的时候,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池(也有可能处理太快,系统优化切换原则,直接使用main线程处理(把sleep去掉))

源码如下

//CompletableFuture.java
public CompletableFuture<Void> thenRun(Runnable action) {//传入值是一样的
        return uniRunStage(null, action);
    }

    public CompletableFuture<Void> thenRunAsync(Runnable action) {
        return uniRunStage(asyncPool, action);//但是这里有个异步的线程池asyncPool
    }

//进入asyncPool
    private static final boolean useCommonPool =
        (ForkJoinPool.getCommonPoolParallelism() > 1);//一般大于1都是成立的(cpu核心)

    /**
     * Default executor -- ForkJoinPool.commonPool() unless it cannot
     * support parallelism.
     */
    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();//所以这里会调用forkJoin线程池
对计算速度进行选用
  • applyToEither(),取快的那个的结果
public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        CompletableFuture<String> play1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            return "play1 ";
        });

        CompletableFuture<String> play2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            return "play2";
        });

        CompletableFuture<String> thenCombineResult = play1.applyToEither(play2, f -> {//对计算速度进行选用
            return f + " is winner";
        });

        System.out.println(Thread.currentThread().getName() + "\t" + thenCombineResult.get());
    }
}
//ForkJoinPool.commonPool-worker-9  ---come in 
//ForkJoinPool.commonPool-worker-2  ---come in 
//main  play2 is winner
对计算结果进行合并
  • thenCombine() ,两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCOmbine来处理先完成的先等着,等待其它分支任务
public class CompletableFutureDemo
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        CompletableFuture<Integer> thenCombineResult = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "---come in 1");
            return 10;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "---come in 2");
            return 20;
        }), (x,y) -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "---come in 3");
            return x + y;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "---come in 4");
            return 30;
        }),(a,b) -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "---come in 5");
            return a + b;
        });
        System.out.println(thenCombineResult.get());

        // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:(或者用自定义线程池)
        try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值