Java多线程并发(1)下-CompletetableFuture

293 阅读4分钟

话说上回,我们认识了基本的线程、进程,Java线程的发展历程,以及JDK1.8中线程实现类CompletetableFuture中创建线程、获取结果集的方法。在下半部分中我们需要进一步了解类中常用的方法。同时为了更好的了解各种方法使用,我会对某些方法进行代码实例讲解,由于JDK1.8更新CompletableFuture同时推出了Lambda表达式,所以对其中代码的理解需要Lambda的基础。

更优的结果获取方法

  • T get(long timeout,TimeUnit unit)

timeout设置等待时间,unit是时钟,当线程等待获取结果的时间超过了timeout,就会抛出异常InterruptedException, ExecutionException, TimeoutException ,TimeUnit是一个枚举类,所以传入它的对象直接传入它的枚举成员。

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
    CompletableFuture.supplyAsync(() -> {try {
        TimeUnit.SECONDS.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }return 1;})
    .get(2, TimeUnit.SECONDS);
}
//这里我们假设需要10s才能完成任务,使用这个方法就能提前终止并抛出异常
  • T getNow(T valueIfAbsent)

当没有结果的时候,返回替补值valueIfAbsent。

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
    System.out.println(new CompletableFuture<Integer>().supplyAsync(() -> {try {
        TimeUnit.SECONDS.sleep(12);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return  1;})
    .getNow(2));;
}
//当执行到getNow的时候,如果并没有返回结果集,就返回参数值valueIfAbsent

对结果进行处理

  • thenApply(Function fc)

对上一步的结果进行任意操作,相当于任务分阶段进行。

  • handle(BiFunction bfc)

thenApply一样,但是它在遇到异常的时候不会终止,而是跳过出错的环节进入函数回调的下一步,并且它的参数是两项,一个参数是任意的(多数是上一次任务的结果),另一个是异常对象。

对结果进行消费

  • thenAccept(Consumer c)

需要上一阶段的结果,返回的CompletableFuturevoid类型的

  • thenRun(Runnable r)

不需要上一阶段的结果,返回的CompletableFuturevoid类型的

CompletableFuture a =  new CompletableFuture<Integer>().supplyAsync(() -> {try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return  1;});
    a.thenAccept((t) -> System.out.println(t));
    System.out.println(a.get());
}
//如果向上述的线程的使用中,没有用连续的方法链(流式的写法),是不会对结果进行消费
//的,因为每一步执行方法返回的CompletableFuture是执行完任务之后新的对象
//所以需要改为
CompletableFuture a =  new CompletableFuture<Integer>().supplyAsync(() -> {try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return  1;})
    .thenAccept((t) -> System.out.println(t));
    System.out.println(a.get());
}
//才会对结果进行真正的消费
//而在链式操作中使用了thenAccept或者thenRun,都会使得结果集被消费,无论链中哪个步骤的get()方法都只会返回null

1.上面两种方法需要等待上一阶段任务完成,同时调用的是上一阶段使用的线程池的同一个线程。

2.对上面两个方法加上后缀Async,那么之前如果使用的是自定义的线程池,使用thenRunAsync一定会走默认线程池。

3.(使用自定义线程池)如果上一阶段任务处理很快,并且main执行了线程池关闭,那么后面的过程不会执行。

4.如果上一阶段处理任务很快,那么后续的thenRun可能会使用main线程。

CompletableFuture
.supplyAsync(() -> {System.out.println(Thread.currentThread().getName());return "s";})
.thenRun(() -> System.out.println(Thread.currentThread().getName()));
有几率输出:
ForkJoinPool.commonPool-worker-9
main

对计算速度进行选用

  • applyToEither(CompletionStage<? extends String> t,Function<? super String,U> f)

t参数放入另一个线程。 f接收线程返回结果(两个线程谁先return就接收谁的),并进行处理。 例如双人对战小游戏中,我们需要两个角色的各种基础逻辑是独立的,但是两个角色谁的血量先低于0谁死亡,那么这个方法就是类似这样的逻辑。

对计算结果进行合并

  • thenCombine(CompletionStage t,BiFunction bf)

将两个线程结果进行合并;bf接收两个线程结算结果。将各科老师阅卷看成各个线程,当一个人的各项成绩出来之后,就会计算总成绩。而这里就体现出线程结果的合并操作。

方法小结

对于CmplettableFuture类,有很多实现的方法,方法核心在于接口CompletionStage,接口中的方法大致分为supply,Function,BiFunction,Consumer,Runnable四种类型,同时还有加上各自方法名加上Async的新方法。这些方法需要我们自己去理解。

本人知识有限,敬请各位指正。