CompletableFuture 学习笔记


【Java并发·03】CompletableFuture入门
【Java并发·04】CompletableFuture进阶
看完后加网上理解的笔记

初次使用

使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。

从Java
8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

俩静态supplyAsync方法,可以有返回结果,而另外俩runAsync是没有返回结果的

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

下面是比对

方法名描述
runAsync(Runnable runnable)使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。
runAsync(Runnable runnable, Executor executor)使用指定的thread pool执行异步代码。
supplyAsync(Supplier<U> supplier)使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作有返回值
supplyAsync(Supplier<U> supplier, Executor executor)使用指定的thread pool执行异步代码,异步操作有返回值

所有方法不带Async和带Async区别(重点理解)

async方法始终尝试取新线程执行方法,当然也可能使用当前线程
不带async方法则会从当前线程里取线程执行

前置工具类

一个小工具类打印线程信息和让线程睡眠

public class SmallTool {

    public static void sleepMillis(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void printTimeAndThread(String tag) {
        String result = new StringJoiner("\t\t")
                .add(new SimpleDateFormat("mm分ss秒SSS").format(new Date()))
                .add("线程id:" + String.valueOf(Thread.currentThread().getId()))
                .add("线程名:" + Thread.currentThread().getName())
                .add(tag)
                .toString();
        System.out.println(result);
    }

}

supplyAsync

场景1:

  1. 小白进入餐厅点菜
  2. 厨师有炒菜和打饭的过程,都是同一个人完成
  3. 在这期间小白再打王者
  4. 做好了开吃
public class _01_supplyAsync {
    public static void main(String[] args) {
        SmallTool.printTimeAndThread("小白进入餐厅");
        SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");

        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("厨师炒菜");
            SmallTool.sleepMillis(200);
            SmallTool.printTimeAndThread("厨师打饭");
            SmallTool.sleepMillis(100);
            return "番茄炒蛋 + 米饭 做好了";
        });

        SmallTool.printTimeAndThread("小白在打王者");
        //等待结果返回输出
        SmallTool.printTimeAndThread(String.format("%s ,小白开吃", cf1.join()));
    }
}
4703085		线程id:1		线程名:main		小白进入餐厅
4703086		线程id:1		线程名:main		小白点了 番茄炒蛋 + 一碗米饭
4703150		线程id:1		线程名:main		小白在打王者
4703150		线程id:11		线程名:ForkJoinPool.commonPool-worker-1		厨师炒菜
4703350		线程id:11		线程名:ForkJoinPool.commonPool-worker-1		厨师打饭
4703455		线程id:1		线程名:main		番茄炒蛋 + 米饭 做好了 ,小白开吃

使用supplyAsync返回结果,内置线程池,最后的语句使用了join阻塞 等待返结果,不然main线程就直接结束了,看不到打印结果。
CompletableFuture 提供了 join() 方法,它的功能和 get() 方法是一样的,都是阻塞获取值,它们的区别在于 join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。

thenCompose

   public <U> CompletableFuture<U> thenComposeAsync(
        Function<? super T, ? extends CompletionStage<U>> fn) {
        return uniComposeStage(asyncPool, fn);
    }
    public interface Function<T, R> {
    	R apply(T t);
    }

允许将两个异步操作进行流水线,第一个操作完成时,将其结果作为参数传递给第二个操作,接收Function<入参类型,CompletionStage出参类型>参数

场景2:

  1. 小白进入餐厅点菜
  2. 厨师负责炒菜
  3. 炒完菜后,服务员才能打饭
  4. 在这期间小白再打王者
  5. 做好了开吃

下面的代码意思就是厨师炒完菜后返回结果给服务员,服务员才能去打饭。

public class _02_thenCompose {
    public static void main(String[] args) {
        SmallTool.printTimeAndThread("小白进入餐厅");
        SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");

        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("厨师炒菜");
            SmallTool.sleepMillis(200);
            return "番茄炒蛋";
        }).thenCompose(dish -> CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("服务员打饭");
            SmallTool.sleepMillis(100);
            return dish + " + 米饭";
        }));

        SmallTool.printTimeAndThread("小白在打王者");
        SmallTool.printTimeAndThread(String.format("%s 好了,小白开吃", cf1.join()));
    }

    /**
     * 嵌套调用,也能实现
     */
    private static void applyAsync() {
        SmallTool.printTimeAndThread("小白进入餐厅");
        SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");

        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("厨师炒菜");
            SmallTool.sleepMillis(200);
            CompletableFuture<String> race = CompletableFuture.supplyAsync(() -> {
                SmallTool.printTimeAndThread("服务员打饭");
                SmallTool.sleepMillis(100);
                return " + 米饭";
            });
            //重点是join()的使用,才能让打饭线程执行完在一起返回
            return "番茄炒蛋" + race.join();
        });

        SmallTool.printTimeAndThread("小白在打王者");
        SmallTool.printTimeAndThread(String.format("%s 好了,小白开吃", cf1.join()));
    }
}

结果,可以看出

2514988		线程id:1		线程名:main		小白进入餐厅
2514989		线程id:1		线程名:main		小白点了 番茄炒蛋 + 一碗米饭
2515056		线程id:11		线程名:ForkJoinPool.commonPool-worker-1		厨师炒菜
2515057		线程id:11		线程名:ForkJoinPool.commonPool-worker-1		服务员打饭
2515057		线程id:1		线程名:main		小白在打王者
2515061		线程id:1		线程名:main		番茄炒蛋 + 米饭 好了,小白开吃

thenCombine

    public <U,V> CompletableFuture<V> thenCombine(
        CompletionStage<? extends U> other,
        BiFunction<? super T,? super U,? extends V> fn) {
        return biApplyStage(null, other, fn);
    }
    public interface BiFunction<T, U, R> {
    	R apply(T t, U u);
    }

thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理,结果由thenCombine 第二个参数BiFunction返回,返回类型如下所示就是R

场景3:

  1. 小白进入餐厅点菜
  2. 厨师炒菜,同时服务员蒸饭
  3. 炒菜蒸饭完成了,服务员才打饭
  4. 在这期间小白再打王者
  5. 做好了开吃

代码意思就是把炒菜蒸饭任务结果一起返回,在进行拼接返回。

public class _03_thenCombine {
    public static void main(String[] args) {
        SmallTool.printTimeAndThread("小白进入餐厅");
        SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");

        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("厨师炒菜");
            SmallTool.sleepMillis(200);
            return "番茄炒蛋";
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("服务员蒸饭");
            SmallTool.sleepMillis(300);
            return "米饭";
        }), (dish, rice) -> {
            SmallTool.printTimeAndThread("服务员打饭");
            SmallTool.sleepMillis(100);
            return String.format("%s + %s 好了", dish, rice);
        });

        SmallTool.printTimeAndThread("小白在打王者");
        SmallTool.printTimeAndThread(String.format("%s ,小白开吃", cf1.join()));

    }

    /**
     * 用 applyAsync 也能实现
     */
    private static void applyAsync() {
        SmallTool.printTimeAndThread("小白进入餐厅");
        SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");

        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("厨师炒菜");
            SmallTool.sleepMillis(200);
            return "番茄炒蛋";
        });
        CompletableFuture<String> race = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("服务员蒸饭");
            SmallTool.sleepMillis(300);
            return "米饭";
        });
        SmallTool.printTimeAndThread("小白在打王者");

        String result = String.format("%s + %s 好了", cf1.join(), race.join());
        SmallTool.printTimeAndThread("服务员打饭");
        SmallTool.sleepMillis(100);

        SmallTool.printTimeAndThread(String.format("%s ,小白开吃", result));
    }
}

结果

5003246		线程id:1		线程名:main		小白进入餐厅
5003247		线程id:1		线程名:main		小白点了 番茄炒蛋 + 一碗米饭
5647452		线程id:13		线程名:ForkJoinPool.commonPool-worker-1		厨师炒菜
5647452		线程id:1		线程名:main		小白在打王者
5647454		线程id:14		线程名:ForkJoinPool.commonPool-worker-2		服务员蒸饭
5647754		线程id:14		线程名:ForkJoinPool.commonPool-worker-2		服务员打饭
5647858		线程id:1		线程名:main		番茄炒蛋 + 米饭 好了 ,小白开吃

可以看出厨师炒菜和服务员蒸饭任务合并,两个都完成之后,在执行BiFunction实现类的内容,就是打饭,然后再返回BiFunction的结果

这个是视频里大佬总结的图,很形象

  • 第一个在于开启异步任务
  • 第二个在于异步任务连接
  • 第三个在于异步任务结合
    对应图

thenApply

把上一个异步执行的结果交给后面的Function,然后返回结果
场景4:

  1. 小白吃完饭要结账,开发票
  2. 服务员收款,这个或另外一个服务员开发票
  3. 回家

下面想要收款和开发票的服务员不同的线程可以用thenApplyAsync,但是用这个也不一定是新线程,原因最上面说过。

    public <U> CompletableFuture<U> thenApplyAsync(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(asyncPool, fn);
    }
public class _01_thenApply {
    public static void main(String[] args) {
        SmallTool.printTimeAndThread("小白吃好了");
        SmallTool.printTimeAndThread("小白 结账、要求开发票");

        CompletableFuture<String> invoice = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("服务员收款 500元");
            SmallTool.sleepMillis(100);
            return "500";
        }).thenApply(money -> {
            SmallTool.printTimeAndThread(String.format("服务员开发票 面额 %s元", money));
            SmallTool.sleepMillis(200);
            return String.format("%s元发票", money);
        });

        SmallTool.printTimeAndThread("小白 接到朋友的电话,想一起打游戏");

        SmallTool.printTimeAndThread(String.format("小白拿到%s,准备回家", invoice.join()));
    }
}
2522554		线程id:1		线程名:main		小白吃好了
2522556		线程id:1		线程名:main		小白 结账、要求开发票
2522630		线程id:11		线程名:ForkJoinPool.commonPool-worker-1		服务员收款 5002522631		线程id:1		线程名:main		小白 接到朋友的电话,想一起打游戏
2522737		线程id:11		线程名:ForkJoinPool.commonPool-worker-1		服务员开发票 面额 5002522938		线程id:1		线程名:main		小白拿到500元发票,准备回家

applyToEither

最快返回输出的线程结果作为下一次任务的输入

    public <U> CompletableFuture<U> applyToEither(
        CompletionStage<? extends T> other, Function<? super T, U> fn) {
        return orApplyStage(null, other, fn);
    }

场景5:

  1. 张三吃完饭,出餐厅,到公交等两路公交
  2. 哪个先到先上哪个
  3. 上车

下面就是700路和800路哪个先到了,就把结果赋给firstComeBus ,然后返回firstComeBus

public class _02_applyToEither {
    public static void main(String[] args) {
        SmallTool.printTimeAndThread("张三走出餐厅,来到公交站");
        SmallTool.printTimeAndThread("等待 700路 或者 800路 公交到来");

        CompletableFuture<String> bus = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("700路公交正在赶来");
            SmallTool.sleepMillis(100);
            return "700路到了";
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("800路公交正在赶来");
            SmallTool.sleepMillis(200);
            return "800路到了";
        }), firstComeBus -> firstComeBus);

        SmallTool.printTimeAndThread(String.format("%s,小白坐车回家", bus.join()));
    }
}
4035908		线程id:1		线程名:main		张三走出餐厅,来到公交站
4035909		线程id:1		线程名:main		等待 700路 或者 800路 公交到来
4035982		线程id:11		线程名:ForkJoinPool.commonPool-worker-1		700路公交正在赶来
4035983		线程id:12		线程名:ForkJoinPool.commonPool-worker-2		800路公交正在赶来
4036086		线程id:1		线程名:main		700路到了,小白坐车回家

exceptionally

当运行出现异常时,调用该方法可进行一些补偿操作,如设置默认值,就是try catch简化版

场景6:

  1. 跟场景5一样,不过在做700路时撞树了
  2. 改叫出租车
    public CompletableFuture<T> exceptionally(
        Function<Throwable, ? extends T> fn) {
        return uniExceptionallyStage(fn);
    }

下面代码就是在搭700路时,抛异常,exceptionally输出异常

public class _03_exceptionally {
    public static void main(String[] args) {
        SmallTool.printTimeAndThread("张三走出餐厅,来到公交站");
        SmallTool.printTimeAndThread("等待 700路 或者 800路 公交到来");

        CompletableFuture<String> bus = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("700路公交正在赶来");
            SmallTool.sleepMillis(100);
            return "700路到了";
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("800路公交正在赶来");
            SmallTool.sleepMillis(200);
            return "800路到了";
        }), firstComeBus -> {
            SmallTool.printTimeAndThread(firstComeBus);
            if (firstComeBus.startsWith("700")) {
                throw new RuntimeException("撞树了……");
            }
            return firstComeBus;
        }).exceptionally(e -> {
            SmallTool.printTimeAndThread(e.getMessage());
            SmallTool.printTimeAndThread("小白叫出租车");
            return "出租车 叫到了";
        });

        SmallTool.printTimeAndThread(String.format("%s,小白坐车回家", bus.join()));
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值