CompletableFuture

CompletableFuture

Future

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

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

  • Future接口中方法

作用

FutureJava5 新加的一个接口,它提供了一种异步并行计算的功能。比如主线程需要执行一个很耗时的计算任务,可以通过future把这个任务放到异步线程中执行,主线程就去做其他事情了,过了一会才去获取子任务的执行结果。举个例子:我在打游戏但是我现在饿了我肯定不能自己去否则就会坑队友会被骂,所以我让朋友帮我带份饭,我可以继续游戏,这就实现了异步任务。

目的

异步多线程任务执行且有返回结果,三个特点:多线程/有返回/异步任务(朋友作为我去买饭 作为新启动的异步多线程任务且买到饭 有结果返回)

FutureTask实现类

上面所说的三个特点在 FutureTasks 类上可以满足

  1. 实现了多线程且有异步任务
  2. 构造器注入 callable 实现了有返回

多线程/有返回/异步 操作

public class FutureTaskTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 异步任务
        FutureTask<String> futureTask = new FutureTask<>(new MyThread());

        Thread thread = new Thread(futureTask, "thread");
        thread.start();

        // 接收返回值 hello callable
        System.out.println(futureTask.get());
    }
}

class MyThread implements Callable<String> {

    @Override
    public String call() {
        System.out.println("com in call()");
        return "hello callable";
    }
}

多线程/有返回/异步 结果

Future 到 CompletableFuture

completableFuture 是由 future 演变而来的,所以我们再来看看 Future 的优缺点

Future 优点

  • future+线程池异步多线程任务配合,能显著提高程序的执行效率。
  • 方案一:3个任务 1 个main线程处理,大概1130ms
public static void main(String[] args) {

    long startTime = System.currentTimeMillis();

    // 暂停毫秒
    try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}
    try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}
    try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}

    long endTime = System.currentTimeMillis();
    System.out.println("消耗时长:" + (endTime - startTime) + " 毫秒");
}
  • 方案二:3个任务,两个 futureTask 执行,一个 main 线程执行,利用线程池(假如每次new一个Thread,太浪费资源,会有GC这些工作),大概400毫秒。
public static void main(String[] args) {

    // 构建指定数量线程池
    ExecutorService threadPool = newFixedThreadPool(3);

    long startTime = System.currentTimeMillis();

    FutureTask<String> futureTask1 = new FutureTask<>(() -> {
        try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}
        return "task1 over";
    });
    threadPool.submit(futureTask1);

    FutureTask<String> futureTask2 = new FutureTask<>(() -> {
        try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}
        return "task2 over";
    });
    threadPool.submit(futureTask2);

    try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}

    long endTime = System.currentTimeMillis();
    System.out.println("消耗时长:" + (endTime - startTime) + " 毫秒");

    threadPool.shutdown();
}

Future 缺点

get() 阻塞

下面模仿阻塞情况,例如在任务中阻塞了五秒,运行会发现最后一段出输出会等待 get() 阻塞完成后才会执行。说白了就是一旦你调用 get() 方法出现了无法计算阻塞时间太长就会导致整个程序无法运行

public static void main(String[] args) throws ExecutionException, InterruptedException {

    FutureTask<String> futureTask = new FutureTask<>(() -> {
        System.out.println(Thread.currentThread().getName() + "\t come in");
        try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
        return "task over";
    });

    Thread thread = new Thread(futureTask, "thread");
    thread.start();

    System.out.println(futureTask.get());

    System.out.println(Thread.currentThread().getName() + "\t 忙其他任务了");
}

针对上方的问题有一种解决方案就是将 get 设置超时时间如果超过指定秒就抛出异常,然后系统再补货异常及时止损。

System.out.println(futureTask.get(3, TimeUnit.SECONDS))

isDone() 轮询

上方使用 get 超时时间直接抛异常不太美观。可以利用if(futureTask.isDone())的方式使得他在结束之后才get(),但是也会消耗cpu

public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {

    FutureTask<String> futureTask = new FutureTask<>(() -> {
        System.out.println(Thread.currentThread().getName() + "\t come in");
        try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
        return "task over";
    });

    Thread thread = new Thread(futureTask, "thread");
    thread.start();

    while (true) {
        if (futureTask.isDone()) {
            System.out.println(futureTask.get());
            break;
        } else {
            // 暂停500毫秒, 要不然会一直刷屏
            TimeUnit.MILLISECONDS.sleep(500);
            System.out.println("正在处理中......");
        }
    }

    System.out.println(Thread.currentThread().getName() + "\t 忙其他任务了");
}

总结

Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果。

Future 应用现状

  1. 对于简单的业务场景使用Future完全OK

  2. 回调通知

    • 应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知
  3. 创建异步任务

    • Future+线程池配合
  4. 多个任务前后依赖可以组合处理(水煮鱼)

    • 想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值,将两个或多个异步计算合成一个异步计算,这几个异步计算相互独立,同时后面这个又依赖前一个处理的结果
    • 比如买鱼-调料-下锅
    • 比如 linux 命令 ps -ef | grep tomcat
  5. 对计算速度选最快完成的(并返回结果)

    • 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果。

CompletableFuture 介绍

  • 阻塞的方式和异步编程的设计理念相违背,而轮询的方式会消耗无畏的CPU资源。因此,JDK8设计出CompletableFuture
  • CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行成后通知监听的一方。

CompletableFuture

  • 在Java 8中, CompletableFuture提供了非常强大的Future的扩展功能, 可以帮助我们简化异步编程的复杂性, 并且提供了函数式编程的能力, 可以通过回调的方式处理计算结果, 也提供了转换和组合CompletableFuture的方法
  • 它可能代表一个明确完成的Future, 也有可能代表一个完成阶段(CompletionStage) , 它支持在计算完成以后触发一些函数或执行某些动作。
  • 它实现了FutureCompletionStage接口

CompletionStage

  • Completion Stage代表异步计算过程中的某一个阶段, 一个阶段完成以后可能会触发另外一个阶段
  • 一个阶段的计算执行可以是一个Function, Consumer或者Runnable。比如:stage.then Apply(x->square(x) ) .then Accept(x->System.out.print(x) ) .then Run() ->System.out.print In() ),
  • 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发

核心的四个静态方法(分为两组)

  1. 利用核心的四个静态方法创建一个异步操作,不建议用new

  2. 四个静态方法分别为(分为两组):

    • 第一组,无返回值
    • 第二组,有返回值
  3. Executor参数说明:

    • 没有指定Executor的方法,直接使用默认的ForkJoinPool.commPool()作为它的线程池执行异步代码。
    • 如果指定线程池,则使用我们定义的或者特别指定的线程池执行异步代码。

runAsSync

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        // 暂停几秒钟线程
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
    });
    // runAsSync 无返回值方法所以 get() 为null
    System.out.println(completableFuture.get());
}

结果

runAsSync + 线程池

public static void main(String[] args) throws ExecutionException, InterruptedException {

    ExecutorService executorService = Executors.newFixedThreadPool(3);

    CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        // 暂停几秒钟线程
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
    }, executorService);

    // runAsSync 无返回值方法所以 get() 为null
    System.out.println(completableFuture.get());

    executorService.shutdown();
}

结果

supplyAsync

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        // 暂停几秒钟线程
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        return "hello supplyAsync";
    });

    // supplyAsync 有返回值:hello supplyAsync
    System.out.println(completableFuture.get());
}

结果

supplyAsync + 线程池

public static void main(String[] args) throws ExecutionException, InterruptedException {

    ExecutorService executorService = Executors.newFixedThreadPool(3);

    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        // 暂停几秒钟线程
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        return "hello supplyAsync";
    }, executorService);

    // supplyAsync 有返回值:hello supplyAsync
    System.out.println(completableFuture.get());

    executorService.shutdown();
}

结果

CompletableFuture 日常使用

从Java8开始引入了CompletableFuture,它是Future的功能增强版,减少阻塞和轮询可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法

基本功能

使用 completableFuture 实现 Future 的功能

public static void main(String[] args) throws ExecutionException, InterruptedException {

    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "come in");
        int result = ThreadLocalRandom.current().nextInt(10);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("一秒钟出结果:" + result);
        return result;
    });

    System.out.println(Thread.currentThread().getName() + "先去忙别的事情了...");

    System.out.println(completableFuture.get());
}

whenComplete

不会因为阻塞而终止程序运行,完美的解决了 Future 中 get()阻塞 和 轮询的问题。

public static void main(String[] args) {

    CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "come in");
        int result = ThreadLocalRandom.current().nextInt(10);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("一秒钟出结果:" + result);
        return result;
    }).whenComplete((v,e) -> {
        // 没有异常, v是值、e是异常
        if(e == null){
            System.out.println("计算后的值为:" + v);
        }
    }).exceptionally(e -> {
        e.printStackTrace();
        System.out.println("异常情况:" + e.getCause()+ " \t " + e.getMessage());
        return null;
    });

    // 线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:暂停3秒钟线程
    System.out.println(Thread.currentThread().getName() + "先去忙别的事情了...");
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

结果

使用自定义线程,就不需要去暂停3秒了,由我们手动关闭

public static void main(String[] args) {
    // 自定义线程池
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "come in");
        int result = ThreadLocalRandom.current().nextInt(10);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("一秒钟出结果:" + result);
        return result;
    }, executorService).whenComplete((v,e) -> {
        // 没有异常, v是值、e是异常
        if(e == null){
            System.out.println("计算后的值为:" + v);
        }
    }).exceptionally(e -> {
        e.printStackTrace();
        System.out.println("异常情况:" + e.getCause()+ " \t " + e.getMessage());
        return null;
    });

    System.out.println(Thread.currentThread().getName() + "先去忙别的事情了...");
    // 关闭线程池
    executorService.shutdown();
}

结果

exceptionally

这是在异常情况下执行的部分,这里伪造一个异常 int i = 10 / 0

public static void main(String[] args) {
    // 自定义线程池
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "come in");
        int result = ThreadLocalRandom.current().nextInt(10);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        int i = 10 / 0;
        System.out.println("一秒钟出结果:" + result);
        return result;
    }, executorService).whenComplete((v,e) -> {
        // 没有异常, v是值、e是异常
        if(e == null){
            System.out.println("计算后的值为:" + v);
        }
    }).exceptionally(e -> {
        e.printStackTrace();
        System.out.println("异常情况:" + e.getCause()+ " \t " + e.getMessage());
        return null;
    });

    System.out.println(Thread.currentThread().getName() + "先去忙别的事情了...");
    // 关闭线程池
    executorService.shutdown();
}

结果

CompletableFuture 优点

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

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

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

CompletableFuture案例精讲

必备技能

函数式接口

  1. 函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
  2. 常见的函数式接口
    • Runnable
    • Function
    • Consumer
    • Supplier
    • Biconsumer(Bi代表两个的意思,我们要传入两个参数,在上面的案例中是v和e)

链式编程

public class Chain {

    public static void main(String[] args) {

        new Student().setId(1).setName("zjh").setMajor("cs");
    }
}

@NoArgsConstructor
@AllArgsConstructor
@Data
@Accessors(chain = true) //开启链式编程
class Student{
    private int id;
    private String name;
    private String major;
}

join 和 get对比

功能几乎一样,区别在于编码时是否需要抛出异常

  • get()方法需要抛出异常
  • join()方法不需要抛出异常
@Test
public void getTest() throws ExecutionException, InterruptedException {
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "hello get");
    System.out.println(completableFuture.get());
}

@Test
public void joinTest() {
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "hello get");
    System.out.println(completableFuture.join());
}

实战精讲-比价网站case

需求

  1. 需求说明
    1.1 同一款产品,同时搜索出同款产品在各大电商平台的售价;
    1.2 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少

  2. 输出返回:
    出来结果希望是同款产品的在不同地方的价格清单列表, 返回一个List
    《mysql》in jd price is 88.05
    《mysql》in dang dang price is 86.11
    《mysql》in tao bao price is 90.43

  3. 解决方案,比对同一个商品在各个平台上的价格,要求获得一个清单列表
    3.1 stepbystep , 一家家查找, 查完京东查淘宝, 查完淘宝查天猫......
    3.2 all in , 同时查找,一口气多线程异步任务同时查询。。。

一家家查找

public class CompletableFutureMallDemo {

    /**
     * 网站集合
     */
    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao")
    );

    /**
     * 一家家搜索
     * @param list          网站集合
     * @param productName   产品名称
     * @return List<String>
     */
    public static List<String> getPrice (List<NetMall> list, String productName) {
        return list.stream()
                // in %s price is %.2f => in taobao price is 90.43
                .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> productList = getPrice(list, "mysql");
        for (String product : productList) {
            System.out.println(product);
        }
        
        // 结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("一家家查找使用时间:" + (endTime - startTime) + " 毫秒");
    }
}

@NoArgsConstructor
@AllArgsConstructor
@Data
class NetMall {

    /**
     * 网站名称
     */
    private String netMallName;

    /**
     * 计算价格
     * @param productName 产品名称
     * @return BigDecimal
     */
    public double calcPrice (String productName) {
        // 睡眠一秒, 模拟查询时间
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        // 返回一个随机的小数
        return ThreadLocalRandom.current().nextDouble() + productName.charAt(0);
    }
}

结果

同时查找

public class CompletableFutureMallDemo {

    /**
     * 网站集合
     */
    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao")
    );

    /**
     * 同时搜索
     * @param list          网站集合
     * @param productName   产品名称
     * @return List<String>
     */
    public static List<String> getPriceByCompletableFuture (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))))
                .collect(Collectors.toList())
                .stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {

        // 开始时间
        long startTime = System.currentTimeMillis();

        List<String> productList = getPriceByCompletableFuture(list, "mysql");
        for (String product : productList) {
            System.out.println(product);
        }

        // 结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("同时查找使用时间:" + (endTime - startTime) + " 毫秒");
    }
}

@NoArgsConstructor
@AllArgsConstructor
@Data
class NetMall {

    /**
     * 网站名称
     */
    private String netMallName;

    /**
     * 计算价格
     * @param productName 产品名称
     * @return BigDecimal
     */
    public double calcPrice (String productName) {
        // 睡眠一秒, 模拟查询时间
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        // 返回一个随机的小数
        return ThreadLocalRandom.current().nextDouble() + productName.charAt(0);
    }
}

结果

CompletableFuture 常用 API

获取结果和主动触发计算

  1. 获取结果
  • public T get() 需要抛异常, 易阻塞

  • public T get(long timeout, TimeUnit unit) 可以设置超时时间,过时抛出异常

  • public T join() 和get()一样,不需要抛异常

  • public T getNow(T valueIfAbsent) 需要设置一个参数也叫备用值,当还没有计算出结果时就会使用我们传入的备用值,不阻塞

  1. 主动触发计算
  • public boolean complete(T value) 和 getNow 差不多也需要传入一个备用值,当计算完成后获取 complete 就返回 false 同时获取的值为计算完成的值。没有计算完成就会返回 true 同时获取的值也是备用值

  • 计算完成后获取

public static void main(String[] args) {

    CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {

        // 暂停一秒钟
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "abc";
    });

    // 暂停两秒再去获取值, 模拟计算完成后获取
    try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) {throw new RuntimeException(e);}
    System.out.println(stringCompletableFuture.complete("completeValue") + "\t" + stringCompletableFuture.join());
}

结果

  • 未计算完成获取
public static void main(String[] args) {

    CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {

        // 暂停一秒钟
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "abc";
    });

    // 直接获取, 模拟未计算完成后获取
    System.out.println(stringCompletableFuture.complete("completeValue") + "\t" + stringCompletableFuture.join());
}

结果

对计算结果进行处理

  1. thenApply:计算结果存在依赖关系,这两个线程串行化。因为是依赖的有一个报错就叫停
public static void main(String[] args) {

    CompletableFuture.supplyAsync(() -> {

        try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {throw new RuntimeException(e);}
        System.out.println("111");
        return 1024;
    }).thenApply(f -> {
        // 模拟异常 由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
        // int i = 10/0;
        System.out.println("222");
        return f + 1;
    }).thenApply(f -> {
        System.out.println("333");
        return f + 1;
    }).whenComplete((v,e) -> {
        System.out.println("计算结果:" + v);
    }).exceptionally(e -> {
        e.printStackTrace();
        System.out.println(e.getMessage());
        return null;
    });

    System.out.println("主线程结束");

    // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
    try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
}
  1. handle:类似于thenApply,虽然也是依赖关系,但是出错不会叫停会继续走
public static void main(String[] args) {

    CompletableFuture.supplyAsync(() -> {

        try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) {throw new RuntimeException(e);}
        System.out.println("111");
        return 1024;
    }).handle((f, e) -> {
        // 模拟异常 虽然存在依赖关系, 但是报错还是继续往下走
        int i = 10/0;
        System.out.println("222");
        return f + 1;
    }).handle((f, e) -> {
        System.out.println("333");
        return f + 1;
    }).whenComplete((v,e) -> {
        System.out.println("计算结果:" + v);
    }).exceptionally(e -> {
        e.printStackTrace();
        System.out.println(e.getMessage());
        return null;
    });

    System.out.println("主线程结束");

    // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
    try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
}

对计算结果进行消费

  1. thenAccept:接收任务的处理结果,并消费处理,无返回结果。
public static void main(String[] args) {

  CompletableFuture.supplyAsync(() -> 1).
          handle((f, e) -> f + 2).
          handle((f, e) -> f + 3).
          thenAccept(System.out::println);
}

任务之间的顺序执行

  • thenRun(thenRun(Runnable runnable)):任务A执行完执行B,并且B不需要A的结果

  • thenAccept(thenAccept(Consumer action)):任务A执行完执行B,B需要A的结果,但是任务B无返回值

  • thenApply(thenApply(Function fn)):任务A执行完执行B,B需要A的结果,同时任务B有返回值

System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {}).join());
// 结果:null 

System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {}).join());
// 结果:null 因为没有返回值

System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB").join());
// 结果:resultA resultB 

CompleteFuture和线程池说明(非常重要)

  • 上面的几个方法都有普通版本和后面加Async的版本

  • 以 thenRun 和 thenRunAsync 为例,有什么区别

public static void main(String[] args) {

    ExecutorService threadPool = Executors.newFixedThreadPool(5);

    CompletableFuture.supplyAsync(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
        return "123";
    },threadPool).thenRun(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
    }).thenRun(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
    }).thenRun(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
    });
}
public static void main(String[] args) {

    ExecutorService threadPool = Executors.newFixedThreadPool(5);

    CompletableFuture.supplyAsync(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
        return "123";
    },threadPool).thenRunAsync(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
    }).thenRun(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
    }).thenRun(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
    });
}
public static void main(String[] args) {

    ExecutorService threadPool = Executors.newFixedThreadPool(5);

    CompletableFuture.supplyAsync(()->{
        // try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
        return "123";
    },threadPool).thenRun(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
    }).thenRun(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
    }).thenRun(()->{
        try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
    });
}
  • 结论
  1. 没有传入自定义线程池,都用默认线程池ForkJoinPool

  2. 传入了一个自定义线程池如果你执行第一个任务的时候,传入了一个自定义线程池

    • 调用thenRun方法执行第二个任务的时候,则第二个任务和第一个任务是用同一个线程池
    • 调用thenRunAsync执行第二个任务的时候,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池
  3. 也有可能处理太快,系统优化切换原则,直接使用main线程处理(把sleep去掉)

  • 源码


    第一张图中可以看到Async比普通的多了一个异步线程池asyncPool,在第二张图中有个三元运算如果为true就左边false就是右边

对计算速度进行选用

  • applyToEither方法,哪个快输出哪个
public static void main(String[] args) throws ExecutionException, InterruptedException {
        
    CompletableFuture<String> play1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("A come in ");
        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        return "playA";
    });

    CompletableFuture<String> play2 = CompletableFuture.supplyAsync(() -> {
        System.out.println("B come in ");
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        return "playB";
    });

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

    System.out.println(Thread.currentThread().getName() + "\t" + result.get());
}

对计算结果进行合并

  • thenCombine 合并。两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCOmbine来处理,先完成的先等着,等待其它分支任务
public static void main(String[] args) {
    CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
        return 10;
    });

    CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
        return 20;
    });

    CompletableFuture<Integer> thenCombineResult = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
        System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
        return x + y;
    });

    System.out.println(thenCombineResult.join());
}
  • 合并版本
public static void main(String[] args) {
    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("主线程结束");
    System.out.println(thenCombineResult.join());


    // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
    try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
}
posted @ 2022-12-27 14:17  橙香五花肉  阅读(95)  评论(0编辑  收藏  举报