CompletableFuture是什么

从名称看来和Future有关,没错,他也是Future的实现,和FutureTask平级,也是用来实现异步线程任务的,并且携带返回值, 具体的使用直接从需求出发,关注下面的需求和实现, 即可掌握

需求

小白来餐厅吃饭, 点了一盘番茄炒蛋+米饭,小白开始打王者,厨师开始炒菜,小白开吃
需求点: 厨师需要单独的线程

实现

编写代码

  1. package com.dance;
  2. import org.junit.jupiter.api.Test;
  3. import java.util.StringJoiner;
  4. import java.util.concurrent.CompletableFuture;
  5. public class CompletableFutureTest {
  6. @Test
  7. public void testOne(){
  8. print("小白进入餐厅");
  9. print("小白点了 番茄炒蛋 + 一碗米饭");
  10. CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
  11. print("厨师炒菜");
  12. sleep(200);
  13. print("厨师打饭");
  14. sleep(100);
  15. return "番茄炒蛋 + 米饭 好了";
  16. });
  17. print("小白在打王者");
  18. print(String.format("%s , 小白开吃", cf1.join()));
  19. }
  20. /**
  21. * 休眠方法
  22. * @param millis 毫秒
  23. */
  24. public static void sleep(long millis){
  25. try {
  26. Thread.sleep(millis);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. /**
  32. * 打印方法
  33. * @param text 文本
  34. */
  35. public static void print(String text){
  36. String str = new StringJoiner("\t|\t")
  37. .add(String.valueOf(System.currentTimeMillis()))
  38. .add(String.valueOf(Thread.currentThread().getId()))
  39. .add(Thread.currentThread().getName())
  40. .add(text)
  41. .toString();
  42. System.out.println(str);
  43. }
  44. }

执行结果

  1. 1649430128924 | 1 | main | 小白进入餐厅
  2. 1649430128924 | 1 | main | 小白点了 番茄炒蛋 + 一碗米饭
  3. 1649430128926 | 1 | main | 小白在打王者
  4. 1649430128927 | 24 | ForkJoinPool.commonPool-worker-19 | 厨师炒菜
  5. 1649430129134 | 24 | ForkJoinPool.commonPool-worker-19 | 厨师打饭
  6. 1649430129244 | 1 | main | 番茄炒蛋 + 米饭 好了 , 小白开吃

厨师用单独的线程去干活了, 异步线程,如此简单

需求进化

在餐厅中一般厨师都只负责炒菜,像打饭这样的事情都是交给服务员来的
需求点:厨师炒完菜后交给服务员,服务员新开线程去打饭

实现

编写代码

@Test
public void testTwo(){
    print("小白进入餐厅");
    print("小白点了 番茄炒蛋 + 一碗米饭");
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        print("厨师炒菜");
        sleep(200);
        return "番茄炒蛋";
    }).thenCompose(dis -> CompletableFuture.supplyAsync(() -> {
        print("服务员打饭");
        sleep(100);
        return dis + " + 米饭 好了";
    }));
    print("小白在打王者");
    print(String.format("%s , 小白开吃", cf1.join()));
}

执行结果

1649431094323    |    1    |    main    |    小白进入餐厅
1649431094324    |    1    |    main    |    小白点了 番茄炒蛋 + 一碗米饭
1649431094326    |    1    |    main    |    小白在打王者
1649431094326    |    24    |    ForkJoinPool.commonPool-worker-19    |    厨师炒菜
1649431094538    |    24    |    ForkJoinPool.commonPool-worker-19    |    服务员打饭
1649431094645    |    1    |    main    |    番茄炒蛋 + 米饭 好了 , 小白开吃

按道理来说这里应该是两个线程的,但是估计我这个执行的太快了,所以后面的任务也提交给了这个线程,我感觉这种代码调用流程就很清晰,看着像Promise

需求进化

小白进入餐厅的时候,开始点菜,要一盘番茄炒蛋+米饭, 但是这个时候米饭是没有蒸好的,需要开始去蒸,所以厨师炒菜,服务员去蒸饭,这两个事情应该是同时进行的,在厨师炒完菜,服务员蒸好饭,厨师将菜交给服务员,服务员打饭,交给小白,小白吃饭
需求点: 厨师炒菜和服务员蒸饭需要同时进行,并且是厨师炒完菜交给服务员

实现

编写代码

@Test
public void testThree(){
    print("小白进入餐厅");
    print("小白点了 番茄炒蛋 + 一碗米饭");
    CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
        print("厨师炒菜");
        sleep(200);
        return "番茄炒蛋";
    }).thenCombine(CompletableFuture.supplyAsync(() -> {
        print("服务员开始蒸饭");
        sleep(300);
        return "米饭";
    }), (dis, rice) -> {
        print("服务员打饭");
        sleep(100);
        return String.format("%s + %s , 好了", dis, rice);
    });
    print("小白在打王者");
    print(String.format("%s , 小白开吃", cf1.join()));
}

执行结果

1649431885256    |    1    |    main    |    小白进入餐厅
1649431885256    |    1    |    main    |    小白点了 番茄炒蛋 + 一碗米饭
1649431885259    |    1    |    main    |    小白在打王者
1649431885259    |    24    |    ForkJoinPool.commonPool-worker-19    |    服务员开始蒸饭
1649431885259    |    25    |    ForkJoinPool.commonPool-worker-5    |    厨师炒菜
1649431885571    |    24    |    ForkJoinPool.commonPool-worker-19    |    服务员打饭
1649431885681    |    1    |    main    |    番茄炒蛋 + 米饭 , 好了 , 小白开吃

这里出现了两个线程,正好可以看到

总结

方法名 描述
supplyAsync 用来开启一个异步任务
thenCompose 用来连接两个有依赖关系的任务,结果由最后一个返回
thenCombine 用来合并两个任务,结果由函数(BiFunction)返回

image.png
第一个任务的需求点在于线程的开启
第二个任务的需求点在于两个线程的连接
第三个任务的需求点在于两个线程的结果合并
怎么样到这里是不是已经简单的入门了呢