线程(十一)---CompletableFuture(二)

写在前面:各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!  

先列一下CompletableFuture 中4个异步执行任务静态方法:
 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    return asyncSupplyStage(asyncPool, supplier);
 }
 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor) {
    return asyncSupplyStage(screenExecutor(executor), supplier);
 }
 public static CompletableFuture<Void> runAsync(Runnable runnable) {
    return asyncRunStage(asyncPool, runnable);
 }
 public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
    return asyncRunStage(screenExecutor(executor), runnable);
 }
  其中supplyAsync用于有返回值的任务,runAsync则用于没有返回值的任务。Executor参数可以手动指定线程池,否则默认ForkJoinPool.commonPool()系统级公共线程池。
  注意:这些线程都是Daemon线程,主线程结束Daemon线程不结束,只有JVM关闭时,生命周期终止。

案例演示:

示例一

future.get()在等待执行结果时,程序会一直阻塞,直到结果返回,也可以调用complete() 方法手动的结束一个Future,调用complete(T t)会立即执行。但是complete(T t)只能调用一次,后续的重复调用会失效。如果future已经执行完毕能够返回结果,此时再调用complete(T t)则会无效。

public static void main(String[] args) throws Exception{
        complete();
}

static void complete() throws Exception{
       CompletableFuture<String> future  = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + ": 开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": 执行结束");
            return "Hello";
        });
        future.complete("World");
        try {
            System.out.println(Thread.currentThread().getName() + " :" + future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
}

执行结果如下:

从执行结果来看,只有主线程执行了,另一个异步线程没有执行,可以看出complete(T value)是立即生效的,并且返回了complete的值value。

改造一下上面的方法,在调用complete前先让主线程sleep三秒,

public static void main(String[] args) throws Exception{
        complete();
}

static void complete() throws Exception{
       CompletableFuture<String> future  = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + ": 开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": 执行结束");
            return "Hello";
        });
        Thread.sleep(3000);
        future.complete("World");
        try {
            System.out.println(Thread.currentThread().getName() + " :" + future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
}

执行结果如下:

从执行结果来看,ForkJoinPool线程池的一个线程执行完之后,主线程才打印,并且打印的是Hello,说明如果future已经执行完毕能够返回结果,此时再调用complete(T t)则会无效。

示例二:串行关系

使用thenApplyAsync实现当前任务正常完成以后执行,当前任务的执行结果可以作为下一任务的输入参数。

static void thenAccept(){
        CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任务A");
        CompletableFuture<String> futureB = futureA.thenApplyAsync(a -> {
            System.out.println("执行任务B.");
            System.out.println("参数:" + a);
            return "a";
        });
        try {
            System.out.println(futureB.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
}

执行结果如下:


     先回顾一下上一篇介绍的then吧,then直译【然后】,也就是表示下一步,可以串行化任务的执行,而apply为function接口方法,有参数,有返回值,所以很容易就从thenApplyAsync方法名看出,这是一个可以使任务串行化执行并且有输入参数和返回值的方法,是不是感觉上一篇介绍的也很有用呢?

上面示例代码中,使用CompletableFuture的supplyAsync方法创建一个有返回值的CompletableFuture(不需要返回值可以使用runAsync()的两个构造方法),调用futureA的thenApplyAsync方法后,就使futureA和futureB串行化执行,并把futureA执行完后的结果当做futureB的参数,最后通过futureB的get()方法获取到futureB的结果。可串行化执行多个任务,如:

static void thenApply1(){
        CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> futureB = futureA.thenApply(s -> s + "World");
        CompletableFuture<String> futureC = futureB.thenApply(String::toLowerCase);
        System.out.println(futureC.join());
}

 串行三个任务,输出结果为helloworld。

相对的,如果不关心上一步的结果,直接执行下一个操作可以使用thenRun()方法。

示例三:聚合(两个/多个)And关系

我们知道combine 和 both 都是要求两者都满足,也就是 and 的关系。聚合关系一般适用于,要得到一个结果,需要依赖前面多个查询的结果的场景,如想要获取商品的当前价,需要先查询原始价格,再查询折扣,才能得到现价,下面使用thenCombine聚合两个独立 Future 的结果,如果不需要返回结果,可以使用thenAcceptBoth(..)。

static void thenCombine() throws Exception{
        Long startTime = System.currentTimeMillis();
        CompletableFuture<Integer> futureA = CompletableFuture.supplyAsync(() -> {
            Integers = null;
            try {
                s =  task1();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return s;
        });
        CompletableFuture<Integer> futureB = CompletableFuture.supplyAsync(() -> {
            Integers = null;
            try {
                s =  task2();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return s;
        });
        CompletableFuture result = futureA.thenCombine(futureB,(a,b) ->{
            return a + b;
        });
        System.out.println(result.get());
        Long endTime = System.currentTimeMillis();
        System.out.println("耗时:" + (endTime - startTime));
        futureA.thenAcceptBoth(futureB,(a,b) ->{
            System.out.println(a + b);
        });
}

static Integertask1() throws Exception{
        System.out.println("任务1开始执行");
        Thread.sleep(2000);
        System.out.println("任务1结束执行");
        return 1;
}

static Integertask2() throws Exception{
        System.out.println("任务2开始执行");
        Thread.sleep(2000);
        System.out.println("任务2结束执行");
        return 2;
}    

  如上通过thenCombine()方法将将两个future聚合起来,通过thenCombine的源码看出他有两个参数,第一个参数为需要聚合的另一个CompletableFuture,第二个参数为BiFunction,BiFunction可以接收连个参数并且有返回值,

 如上演示代码调用CompletableFuture的thenCombine()方法,传入futureB,和两个future的返回结果,计算结果为3

执行结果如下:

从结果来看,thenCombine实现了连个CompletableFuture的聚合,并计算出了他们的结果之和,并且从时间上来看只用了2047毫秒,效率提示了一倍,平时这种场景还是比较多的,但是遗憾的是thenCombine只能聚合两个CompletableFuture的结果。如果想要聚合多个结果,CompletableFuture也是可以实现的,可以使用 allOf()方法,

由源码看出,聚合多少个都可以,但是没有返回值,下面模拟一个场景,查询一个商品详情,需要分别去查商品信息,卖家信息,库存信息,订单信息等,这些查询相互独立,在不同的服务上, 假设每个查询都需要一到两秒钟,要求总体查询时间小于2秒,看如下代码:

static void allOf(){
        ExecutorService executorService = Executors.newCachedThreadPool();
        Long startTime = System.currentTimeMillis();
        CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "商品详情";
        },executorService);
        CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "卖家信息";
        },executorService);
        CompletableFuture<String> futureC = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "库存信息";
        },executorService);
        CompletableFuture<String> futureD = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "订单信息";
        },executorService);
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(futureA,futureB,futureC,
                futureD);
        allFuture.join();
        System.out.println(futureA.join() + "---" + futureB.join() + "---" + futureC.join() +
                "---" + futureD.join());
        Long endTime = System.currentTimeMillis();
        System.out.println("耗时:" + (endTime - startTime));
}

执行结果如下:

可以看出,使用allOf聚合了四个CompletableFuture并且也达到了预期的效率。

提到allOf:当所有的CompletableFuture都执行完后执行计算,还有一个anyOf,它表示最快的那个CompletableFuture执行完之后执行计算,就不再贴代码了。

示例四:聚合(两个/多个)Or关系

我们知道Either 表示两者中的一个,自然也就是 Or 的体现了,如示例三中,先介绍一下可以聚合处理两个CompletableFuture的方法,如applyToEither():执行两个CompletionStage的结果,哪个先执行完了,就是用哪个的返回值进行下一步操作。

演示代码如下:

static void applyToEither(){
        CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "通过方式A获取结果";
        });
        CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "通过方式B获取结果";
        });
        CompletableFuture<String> futureC = futureA.applyToEither(futureB,
                result -> "结果:" + result);
        System.out.println(futureC.join());
}

无论执行多少次返回结果都是B,PS:join()、get()两个方法都是获取结果。类似于thenCombine 不需要返回结果时可使用acceptEither(),如果从多个future选取最快的一个可以使用anyOf()方法。

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值