CompletableFuture的理解(附代码示例)

概念

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

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

它实现了Future和CompletionStaqe接口。

优点:

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

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

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

runAsync与supplyAsync的区别

CompletableFuture.runAsync:无返回值,不使用自定义线程池时,使用默认线程池ForkJoinPool.commonPool

CompletableFuture.supplyAsync:有返回值,不使用自定义线程池时,使用默认线程池ForkJoinPool.commonPool

@Test
    public void s1() throws ExecutionException, InterruptedException {
        //创建线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10, 1L,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(50),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        
        //无返回值,使用默认线程池ForkJoinPool.commonPool
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "----come in");
        });
        
        //无返回值,使用线程池
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "----come in");
        }, threadPoolExecutor);
        System.out.println(future2.get()); //null

        //有返回值
        CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "---come in");
            return 10;
        });
        System.out.println(future3.get());//10

        CompletableFuture<Integer> future4 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "---come in");
            return 11;
        }, threadPoolExecutor);
        System.out.println(future4.get());//11
        //关闭线程池
        threadPoolExecutor.shutdown();
    }
}

CompletableFuture的API

1. 获得结果与触发计算

getNow,如果计算完结果,则用计算结果;否则用给定的值

complete,如果计算完结果则打断失败,获取计算结果;否则打断计算,使用给定的值

 @Test
    public void m1() throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,10,1L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(50), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        }, threadPoolExecutor);

        //3秒后获取结果
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
      //  System.out.println(future.getNow(999));//如果计算完结果,则用计算结果;否则用给定的值
        System.out.println(future.complete(-44)+"\t"+future.get());//如果计算完结果则打断失败,获取计算结果;否则打断计算,使用给定的值
        threadPoolExecutor.shutdown();
    }

以complete()为例

要求3秒后获取结果,计算只要2秒,所以直接使用计算结果

 

将需求改为1秒获取结果,计算仍要2秒,所有得不到计算结果,complete()返回true,并使用给定的-44

 

2. 对计算结果进行处理,有返回结果(针对同一线程)

thenApply,存在依赖关系,当前步错,不走下一步,当前步有异常就叫停

handle,当前步错,也可以走下一步,带着有异常的参数走

whenComplete(v,e)v:最终结果 e:异常

当无异常时,可以连续执行,将结果一步步赋给下一步,直到最后

@Test
    public  void m2() {
        System.out.println(CompletableFuture.supplyAsync(() -> {
            System.out.println("-------1");
            return 1;
        }).thenApply(f -> {
            //int i = 10/0;
            System.out.println("-------2"); //thenApply存在依赖关系,当前步错,不走下一步,当前步有异常就叫停
            return f + 1;
        }).thenApply(f -> {
            System.out.println("-------3");
            return f + 2;
        }).whenComplete((v, e) -> {
            if (e == null) {
                System.out.println("----result: " + v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            return null;
        }).join());
}

 当有异常时,thenApply只执行到出错的那一步

 当有异常时,handle也可以走下一步,带着有异常的参数走

System.out.println(CompletableFuture.supplyAsync(() -> {
            System.out.println("-------1");
            return 1;
        }).handle((f,e) -> {
            int i = 10/0;
            System.out.println("-------2"); //handle,当前步错,也可以走下一步,带着有异常的参数走
            return f + 1;
        }).handle((f,e) -> {
            System.out.println("-------3");
            return f + 2;
        }).whenComplete((v, e) -> {
            if (e == null) {
                System.out.println("----result: " + v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            return null;
        }).join());

 3. 对计算结果消费,无返回结果

thenRun,无输入,无返回值

thenApply,有输入,有返回值

thenAccept,有输入,无返回值

@Test
    public  void m3() {
        CompletableFuture.supplyAsync(()->{
            return 1;
        }).thenApply( f -> {
            return f+1;
        }).thenApply( f -> {
            return f+2;
        }).thenAccept(c -> {
            System.out.println(c);
        });

        //无输入,无返回值
        System.out.println(CompletableFuture.supplyAsync(()-> "resultA").thenRun(()->{}).join());
        //有输入,有返回值
        System.out.println(CompletableFuture.supplyAsync(()-> "resultB").thenApply(resultB->resultB+" resultC").join());
        //有输入,无返回值
        System.out.println(CompletableFuture.supplyAsync(()-> "resultD").thenAccept(resultD ->{}).join());
    }

  4. 对计算速度进行选用

applyToEither,比较计算速度,选择速度较快的作为返回结果

 1号玩家完成任务需要1秒,2号玩家完成任务需要2秒,故判断1号玩家赢。(两个玩家线程都是需要执行的,再判断速度取结果)

  @Test
    public  void m4() throws InterruptedException {
        System.out.println(CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("1号玩家");
            return 1;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("2号玩家");
            return 2;
        }), r -> {
            System.out.println(r+"号玩家赢");
            return r;
        }).join());

        TimeUnit.SECONDS.sleep(3);
    }

 5. 对计算结果进行合并

thenCombine,合并两个线程的结果

先计算0-10的和,再计算10-20的和,两者合并相加,在与40进行合并相加

 @Test
    public  void m5() throws InterruptedException {
        System.out.println(CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int s1 = 0;
            for (int i = 0; i < 10; i++) {
                s1 += i;
            }
            return s1;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int s2 = 0;
            for (int i = 10; i < 20; i++) {
                s2 += i;
            }
            return s2;
        }), (r1, r2) -> {
            return r1 + r2;
        }).thenCombine(CompletableFuture.supplyAsync(()->{
            return 40;
        }),(r1,r2)->{
            return r1+r2;
        }).join());//还可继续合并

        TimeUnit.SECONDS.sleep(3);
    }

 6. 对计算结果进行处理,有返回结果(针对不同线程)

thenCompose,与thenApply类似,有输入,有返回值,但是输入是针对不同的线程之间

  @Test
    public void m6() {
        System.out.println(CompletableFuture.supplyAsync(() -> {
            return 1;
        }).thenCompose(f -> CompletableFuture.supplyAsync(() -> {
            return f + 1;
        })).join());
    }

 

CompletableFuture的实例

需求:同一款产品,同时搜索出同款产品在各大电商的售价;

结果:希望是同款产品的在不同地方的价格清单列表,返回- - list

比较:将同步实现的方法与异常实现的方法进行对比

public class CompletableFutureNetMallDemo {
    //模拟不同平台
    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("pdd"),
            new NetMall("pdd"),
            new NetMall("pdd"),
            new NetMall("pdd"),
            new NetMall("pdd"),
            new NetMall("pdd"),
            new NetMall("pdd"),
            new NetMall("tmall")
    );

    //同步完成 step by step
    public static List<String> getPriceByStep(List<NetMall> list, String bookName) {
        return list.stream().map(netMall -> String.format
                (bookName + "in %s price is %.2f", netMall.getMallName(), netMall.getPrice(bookName)))
                .collect(Collectors.toList());
    }

    //异步完成 多箭齐发
    public static List<String> getPriceByASync(List<NetMall> list,String bookName){
        return list.stream().map(netMall -> CompletableFuture.supplyAsync(()-> String.format
                (bookName + "in %s price is %.2f", netMall.getMallName(), netMall.getPrice(bookName))))
                .collect(Collectors.toList()) //List<CompletableFuture<String>>
                .stream().map(CompletableFuture::join).collect(Collectors.toList());//List<String>
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        List<String> list1 = getPriceByStep(list, "mysql");
        for (String element : list1){
            System.out.println(element);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("----byStep:"+(endTime-startTime)+"毫秒");

        long startTime2 = System.currentTimeMillis();
        List<String> list2 = getPriceByASync(list, "mysql");
        for (String element : list2){
            System.out.println(element);
        }
        long endTime2 = System.currentTimeMillis();
        System.out.println("----byASync:"+(endTime2-startTime2)+"毫秒");

    }
}

class NetMall {
    @Getter
    private String mallName;

    public NetMall(String mallName) {
        this.mallName = mallName;
    }

    //模拟获取价格
    public Double getPrice(String bookName) {
        return ThreadLocalRandom.current().nextDouble() * 2 + bookName.charAt(0);
    }
}

比较结果可发现,同步方法step by step消耗时间远超异步方法。(数据规模越大,越明显)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值