CompletableFuture的学习

65 阅读5分钟

CompletableFuture中的四个核心静态方法用来获取CompletableFuture对象

  1. runAsync 方法无返回值

runAsync(Runnable runnable)

runAsync(Runnable runnable, Executor executor)

  1. supplyAsync 方法有返回值

supplyAsync(Supplier<U> supplier)

supplyAsync(Supplier<U> supplier,Executor executor)

其中第二个参数为线程池对象,如果不提供线程池,会采用默认的线程池

    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        return "hello supplyAsync";
    });

    System.out.println(completableFuture.get());
    // 此时输出结果为
    // ForkJoinPool.commonPool-worker-1
    // hello supplyAsync

提供了线程池对象后

    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        return "hello supplyAsync";
    }, threadPool);

    System.out.println(completableFuture.get());
    threadPool.shutdown();
    // 此时输出结果为
    // pool-1-thread-1
    // hello supplyAsync

通用异步编程

依旧是上面的程序,不过这次多了几个步骤

whenComplete方法从以下代码可以看出,接口中需要两个参数,一个是上个方法调用后的返回值,另一个则是错误信息

whenComplete(BiConsumer<? super T, ? super Throwable> action)

exceptionally方法里面的需要传入一个函数,并且函数的类型正好是异常类

exceptionally(Function<Throwable, ? extends T> fn)

    CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "正在运行");
        // 获得一个 10 以内随机的数字
        int result = ThreadLocalRandom.current().nextInt(10);
        try {
            // 模拟程序的执行过程 
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("输出结果为" + result);
        return result;
        // value 表示上一个方法的返回值,error 表示异常
    }).whenComplete((value, error) -> {
        if (error == null) {
            System.out.println("计算完成,结果耗时:" + value);
        }
        // 这里的 error 就是异常
    }).exceptionally(error -> {
        error.printStackTrace();
        System.out.println("异常情况:" + error.getCause() + "\t" + error.getMessage());
        return null;
    });

    System.out.println("main线程正在运行...");
    // 输出结果为
    // ForkJoinPool.commonPool-worker-1正在运行
    // main线程正在运行...

上面代码执行时,whenComplete并没有执行,因为使用默认的线程池如同守护线程一般,主线程结束后就会自动关闭。可以使用功能自定义的的线程池解决这个问题

    // 创建线程池对象
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    try {
        CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "正在运行");
            int result = ThreadLocalRandom.current().nextInt(10);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("输出结果为" + result);
            return result;
        }, threadPool).whenComplete((value, error) -> {
            if (error == null) {
                System.out.println("计算完成,结果耗时:" + value);
            }
        }).exceptionally(error -> {
            error.printStackTrace();
            System.out.println("异常情况:" + error.getCause() + "\t" + error.getMessage());
            return null;
        });
        System.out.println("main线程正在运行...");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 关闭线程池
        threadPool.shutdown();
    }
    
    // 输出结果为
    // pool-1-thread-1正在运行
    // main线程正在运行...
    // 输出结果为2
    // 计算完成,结果耗时:2

CompletableFuture中的常用方法

获取结果

  1. get 和 join 都可以获取返回值,但 get 方法使用时需要捕捉异常,而 join 方法不需要
    CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "\t" + "线程执行了");
        return true;
    });
    try {
        // 输出结果
        // ForkJoinPool.commonPool-worker-1	线程执行了
        // true
        System.out.println(future.get());
    } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
    }
    CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
        // 输出结果
        // ForkJoinPool.commonPool-worker-1	线程执行了
        // true
        System.out.println(Thread.currentThread().getName() + "\t" + "线程执行了");
        return true;
    });
    System.out.println(future.join());
  1. 有参数的 get 后面跟时间,表示等待的时间,超过后会抛出异常

    get(long timeout, TimeUnit unit)

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello,World!";
    });
    try {
        // 当时间为 1 秒时会抛出 TimeOut 异常
        // future.get(1, TimeUnit.SECONDS);
        System.out.println(future.get(3, TimeUnit.SECONDS));
        // 当设置为 3 秒时,正常输出 Hello,World!
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
        e.printStackTrace();
    }
  1. getNow 方法在没有计算完成时会返回传入的值,计算完成后会返回计算之后的结果值
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello,World!";
    });
    System.out.println(future.getNow("Not Value"));
    // 输出 Not Value

    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(future.getNow("Not Value"));
    // 输出 Hello,World!
  1. complete 方法返回 bool ,表示是否打断了当前线程任务,并返回结果
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello,World!";
    });
    // System.out.println(future.complete("Not Value"));
    // System.out.println(future.join());
    // true
    // Not Value
    try {
        Thread.sleep(1500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(future.complete("Not Value"));
    System.out.println(future.join());
    // false
    // Hello,World!

对计算结果进行处理

  1. thenApply 计算结果存在依赖关系,两个线程串行化,出异常会中断
    CompletableFuture.supplyAsync(() -> {
        System.out.println("第一步");
        return 10;
    }).thenApply((f) -> {
        // 模拟异常
        int i = 10 / 0;
        System.out.println("第二步");
        return f + 10;
    }).thenApply((f) -> {
        System.out.println("第三步");
        return f + 10;
    }).whenComplete((val, err) -> {
        System.out.println("第四步");
        System.out.println(val);
    }).exceptionally((err) -> {
        err.printStackTrace();
        System.out.println(err.getMessage());
        return null;
    });
    // 未出异常的结果      出现异常结果
    //  第一步              第一步
    //  第二步              第四步
    //  第三步              null
    //  第四步              
    //  30
  1. handle 出现异常不会中断
    CompletableFuture.supplyAsync(() -> {
        System.out.println("第一步");
        return 10;
    }).handle((f, e) -> {
        // 模拟异常
        int i = 10 / 0;
        System.out.println("第二步");
        return f + 10;
    }).handle((f, e) -> {
        System.out.println("第三步");
        return f + 10;
    }).whenComplete((val, err) -> {
        System.out.println("第四步");
        System.out.println(val);
    }).exceptionally((err) -> {
        err.printStackTrace();
        System.out.println(err.getMessage());
        return null;
    });
    // 未出异常的结果      出现异常结果
    //  第一步              第一步
    //  第二步              第三步
    //  第三步              第四步
    //  第四步              null
    //  30

对计算结果进行消费

  1. thenAccept 对结果进行消费
    CompletableFuture.supplyAsync(() -> 1).
            thenApply((f) -> f + 1).
            thenAccept(System.out::println);
            // 输出结果为 2

线程池运行选择

使用了自定义的线程池后,使用 thenRun 都是使用的同一个线程池

    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "执行了");
        return -1;
    }, threadPool).thenRun(() -> System.out.println(Thread.currentThread().getName() + "执行了")
    ).thenRun(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
    threadPool.shutdown();
    // 输出结果
    // pool-1-thread-1执行了
    // pool-1-thread-1执行了
    // pool-1-thread-1执行了

当使用 thenRunAsync 后,后面的线程不一定就是原来的线程池对象了

    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + "执行了");
        return -1;
    }, threadPool).thenRunAsync(() -> System.out.println(Thread.currentThread().getName() + "执行了")
    ).thenRun(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
    threadPool.shutdown();
    // 输出结果
    // pool-1-thread-1执行了
    // ForkJoinPool.commonPool-worker-1执行了
    // ForkJoinPool.commonPool-worker-1执行了

对计算速度进行选用

applyToEither 方法谁快,传入的参数为运行快的线程的哪一个结果

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

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

    CompletableFuture<String> result = gamerA.applyToEither(gamerB, (f) ->
            f + " is winner"
    );

    System.out.println(Thread.currentThread().getName() + "\t" + result.join());
    // 输出结果
    // A come in...
    // B come in...
    // main	gamer A is winner

对计算结果进行合并

thenCombine 方法有两个参数,一个是另外一个线程对象,另一个则是 BiFunction 接口,有两个参数函数接口,两个参数分别对应两个线程对象的结果

    CompletableFuture<Integer> thenCombine = 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;
    }), (x, y) -> {
        System.out.println(Thread.currentThread().getName() + "\t" + "---come in 5");
        return x + y;
    });

    System.out.println(Thread.currentThread().getName() + "\t" + thenCombine.join());
    // 输出结果
    // ForkJoinPool.commonPool-worker-1	---come in 1
    // ForkJoinPool.commonPool-worker-2	---come in 2
    // ForkJoinPool.commonPool-worker-3	---come in 4
    // ForkJoinPool.commonPool-worker-2	---come in 3
    // ForkJoinPool.commonPool-worker-2	---come in 5
    // main	60