JDK8探险——CompletableFuture

Java的线程不只是Thread,调度不止是.start、.join、.sleep。
jdk5引入Future、Callable,多线程调度有了很多高级应用。JDK8开始之后引入lambda,程序调度流程粒度进一步缩小,配合新的线程相关API,出现了很多看起来优雅,功能强大,使用要求高的线程调度用法。
CompletableFuture 方法探析。

1. supplyAsync

相关方法

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) 

CompletableFuture的静态方法,接受一个Function生成一个新的CompletableFuture对象,对象生成时异步执行传入的Function;< U >泛型为接受的Function的返回值类型。

查看源码如下:

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    return asyncSupplyStage(asyncPool, supplier);
}

指定默认的线程池asyncPool调用了asyncSupplyStage方法返回CompletableFuture,asyncPool是CompletableFuture私有静态属性。supplyAsync有一个重载方法,可以指定执行的连接池。【supplyAsync(Supplier< U > supplier, Executor executor)】
关于asyncSupplyStage方法如下:

static <U> CompletableFuture<U> asyncSupplyStage(Executor e,Supplier<U> f){
    if (f == null) throw new NullPointerException();
    CompletableFuture<U> d = new CompletableFuture<U>();
    e.execute(new AsyncSupply<U>(d, f));
    return d;
}

初始化一个CompletableFuture< U >对象,和传入的Function一同包装成一个AsyncSupply对象,根据继承关系,AsyncSupply是Runnable接口的实现类。在AsyncSupply重写run方法,作用就是将Fanction结果的返回值设置到CompletableFuture对象中,完成后继续下一个任务。

public void run() {
      CompletableFuture<T> d; Supplier<T> f;
      if ((d = dep) != null && (f = fn) != null) {
          dep = null; fn = null;
          if (d.result == null) {
              try {
                  d.completeValue(f.get());
              } catch (Throwable ex) {
                  d.completeThrowable(ex);
              }
          }
          d.postComplete();
       }
  }

综上所知:asyncSupplyStage方法中e.execute执行时会初始化一个Thread执行操作,传入的Function是异步执行不阻塞主线程。测试代码如下:

@Test
public void supplyAsync() {
    CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1 Say hello!" + Thread.currentThread());
        return "hello";
    });
    CompletableFuture.supplyAsync(() -> {
        System.out.println("2 Say hello!" + Thread.currentThread());
        return "hello";
    });
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

运行结果:.

2 Say hello!Thread[ForkJoinPool.commonPool-worker-2,5,main]
1 Say hello!Thread[ForkJoinPool.commonPool-worker-1,5,main]
2.thenApplyAsync,变换

相关方法

public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn,Executor executor);

使用示例:

@Test
public void thenApply() throws InterruptedException, ExecutionException {
    CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> "12");

    CompletableFuture<Integer> thenApply = supplyAsync.thenApplyAsync(s -> {
        System.out.println("String:" + s);
        return Integer.valueOf(s);
    });

    Integer result = thenApply.get();
    System.out.println("int:" + result);
}

方法的入参fn是一个函数式接口,这个fn的入参是上一步的计算结果“12”,get()会阻塞主线程等待执行结果返回,最终打印结果:

String:12
int:12
3.thenAcceptAsync,消耗

相关方法

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

从方法名含义上理解,接受上一个执行结果,应用一个没有返回值(Void)的Consumer。
测试用例:

@Test
public void thenAccept() {
    CompletableFuture.supplyAsync(() -> "hello").thenAcceptAsync(s -> {
        System.out.println(s + " world");
    });
}

虽然入参的Consumer对象是不期待返回值的,但是thenAcceptAsync方法是有返回值的。

4.thenRunAsync

相关方法

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

在上一步执行结束之后,启动的一个线程,入参是一个Runnable类型的,不需要入参和返回值。和thenAccept相比,不需要之前执行结果的依赖。
测试方法

@Test
public void thenRun() {
    CompletableFuture.supplyAsync(() -> {
        try {
            System.out.println(Thread.currentThread());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello";
    }).thenRun(() -> {
        System.out.println("hello world in " + Thread.currentThread());
        System.exit(1);
    });
    while (true) {
        System.out.println("while");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

Thread[ForkJoinPool.commonPool-worker-1,5,main]
while
while
while
while
while
while
while
while
while
while
hello world in Thread[ForkJoinPool.commonPool-worker-1,5,main]

从结果上看,Runnable 使用的线程池和supplyAsync应用的是同一个。

5.thenCombine,结合

相关方法:

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

获取上一个的返回结果,结合other的返回结果,两个结果作为参数共同传入BiFunction中计算一个最终结果。
测试方法:

@Test
public void thenCombine() {
    String result = CompletableFuture.supplyAsync(() -> {
        try {
            System.out.println("hello in " + Thread.currentThread());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello";
    }).thenCombine(CompletableFuture.supplyAsync(() -> {
        try {
            System.out.println("world in " + Thread.currentThread());
            Thread.sleep(3000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "world";
    }), (s1, s2) -> {
        System.out.println("hello world in " + Thread.currentThread());
        return s1 + " " + s2;
    }).join();
    System.out.println("main get result:"+result);
}

执行结果:

hello in Thread[ForkJoinPool.commonPool-worker-1,5,main]
world in Thread[ForkJoinPool.commonPool-worker-2,5,main]
hello world in Thread[ForkJoinPool.commonPool-worker-2,5,main]
main get result:hello world

分任务在不同的线程里并行执行,第二个字任务和汇总任务因为都是thenCombine的入参,在同一个线程中进行。

6.thenAcceptBothAsync

相关方法:

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action,     Executor executor);

thenCombine的无返回值版本。

7.runAfterBoth

相关方法:

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);

thenAcceptBothAsync的无入参版。不关注上一个和other的执行结果。

8.applyToEither,选一个快的

相关方法:

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);

测试方法:

@Test
public void applyToEither() {
    String result = CompletableFuture.supplyAsync(() -> {
        int sleep =  (int)(Math.random()*10);
        System.out.println(String.format("hello sleep %s s", sleep));
        try {
            Thread.sleep(sleep*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello";
    }).applyToEither(CompletableFuture.supplyAsync(() -> {
        int sleep =  (int)(Math.random()*10);
        System.out.println(String.format("hello world %s s", sleep));
        try {
            Thread.sleep(sleep*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "world";
    }), s -> s).join();
    System.out.println("main get result:"+result);
}

整体逻辑是,最终方法需要一个入参去计算结果,获得这个参数有两种途径,哪个快就用哪个的计算结果作为参数计算最终结果,慢的被丢弃。

测试输出结果:

hello sleep 9 s
hello world 3 s
main get result:world
9.acceptEither & runAfterEither

同样的,applyToEither的Void版acceptEither和无参Void版runAfterEither。
相关方法:

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);
10. exceptionally,异常补偿

相关方法

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

代码发生异常时,进行补偿,类似try-catch的功能。
测试方法:

@Test
public void exceptionally() {
    String result = CompletableFuture.supplyAsync(() -> {
        try {
            System.out.println(Thread.currentThread());
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (1 == 1) {
            throw new RuntimeException("测试一下异常情况");
        }
        return "s1";
    }).exceptionally(e -> {
        System.out.println(Thread.currentThread());
        System.out.println(e.getMessage());
        return "hello world";
    }).join();
    System.out.println(result);
}

输出结果:

Thread[ForkJoinPool.commonPool-worker-1,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
java.lang.RuntimeException: 测试一下异常情况
hello world

从打印线程信息看,异常处理部分的方法执行和发生异常的代码在同一个线程中执行。

11. whenComplete 结束记录。

相关方法:

public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action);
public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,Executor executor);

接收执行完成的结果,返回值或异常,不会影响最终执行结果。
与exceptionally进行比较,测试代码:

@Test
public void whenComplete() {
    String result = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "s1";
    }).whenComplete((s, t) -> {
        System.out.println(s);
        System.out.println(t);
        int i = 1/0;
    }).exceptionally(e -> {
        System.out.println(e.getMessage());
        return "hello world";
    }).join();
    System.out.println(result);
}

输出测试结果:

s1
null
java.lang.ArithmeticException: / by zero
hello world

注意whenComplete中参数的空指针问题。
另一种情况:

@Test
public void whenComplete() {
    String result = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }int i = 1/0;
        return "s1";
    }).whenComplete((s, t) -> {
        System.out.println(s);
        System.out.println(t);
    }).exceptionally(e -> {
        System.out.println(e.getMessage());
        return "hello world";
    }).join();
    System.out.println(result);
}

执行结果:

null
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
hello world

可见,whenComplete只是将前一个的执行结果,无论是异常还是正常结果,传递到下面去。

12.handle,结果处理

相关方法:

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

所谓结果,包含两种,一种是喜闻乐见的正常结果,另一种是“喜闻乐见”的异常结果。
发生异常测试:

@Test
public void handle() {
    String result = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //出现异常
        int i =1/0;
        return "s1";
    }).handle((s, t) -> {
        if (t != null) {
            return "s2";
        }
        return s;
    }).join();
    System.out.println(result);
}

执行结果:
s2
无异常测试:

@Test
public void handle() {
    String result = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "s1";
    }).handle((s, t) -> {
        if (t != null) {
            return "s2";
        }
        return s;
    }).join();
    System.out.println(result);
}

结果:

s1

对比whenComplete,它功能更加强大,增加了可以影响最终结果的功能。

以上只是简单的单个或两个的结合方法的测试,在实际的业务中所需处理的逻辑只会更加复杂。CompletableFuture类的泛型和流式调用设计,在复杂业务的任务流程调用上有一定的便利。

参考:
简书:数齐—CompletableFuture 详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值