Java8——异步编程

Java8——异步编程runAsync方法可以在后台执行异步计算,但是此时并没有返回值。持有一个Runnable对象。 此时我们看到返回的是CompletableFutureT此处的T就是你想要的返回值的类型。其中的SupplierT是一个简单的函数式接口。 异步任务也是有返回值的,当我们想…

Java8——异步编程

异步编程

所谓异步其实就是实现一个无需等待被调用函数的返回值而让操作继续运行的方法

创建任务并执行任务

无参创建

1 CompletableFuture<String> noArgsFuture = new CompletableFuture<>();

传入相应任务,无返回值

runAsync方法可以在后台执行异步计算,但是此时并没有返回值。持有一个Runnable对象。

1CompletableFuture noReturn = CompletableFuture.runAsync(()->{
2    //执行逻辑,无返回值
3});

传入相应任务,有返回值

此时我们看到返回的是CompletableFuture<T>此处的T就是你想要的返回值的类型。其中的Supplier<T>是一个简单的函数式接口。

1CompletableFuture<String> hasReturn = CompletableFuture.supplyAsync(new Supplier<String>() {
2    @Override
3    public String get() {
4        return "hasReturn";
5    }
6});

此时可以使用lambda表达式使上面的逻辑更加清晰

1CompletableFuture<String> hasReturnLambda = CompletableFuture.supplyAsync(TestFuture::get);
2
3private static String get() {
4    return "hasReturnLambda";
5}

获取返回值

异步任务也是有返回值的,当我们想要用到异步任务的返回值时,我们可以调用CompletableFutureget()阻塞,直到有异步任务执行完有返回值才往下执行。

我们将上面的get()方法改造一下,使其停顿十秒时间。

 1private static String get() {
 2    System.out.println("Begin Invoke getFuntureHasReturnLambda");
 3    try {
 4        Thread.sleep(10000);
 5    } catch (InterruptedException e) {
 6
 7    }
 8    System.out.println("End Invoke getFuntureHasReturnLambda");
 9    return "hasReturnLambda";
10}

然后进行调用

1public static void main(String[] args) throws ExecutionException, InterruptedException {
2    CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
3    System.out.println("Main Method Is Invoking");
4    funtureHasReturnLambda.get();
5    System.out.println("Main Method End");
6}

可以看到输出如下,只有调用get()方法的时候才会阻塞当前线程。

1Main Method Is Invoking
2Begin Invoke getFuntureHasReturnLambda
3End Invoke getFuntureHasReturnLambda
4Main Method End

自定义返回值

除了等待异步任务返回值以外,我们也可以在任意时候调用complete()方法来自定义返回值。

 1CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
 2System.out.println("Main Method Is Invoking");
 3new Thread(()->{
 4    System.out.println("Thread Is Invoking ");
 5    try {
 6        Thread.sleep(1000);
 7        funtureHasReturnLambda.complete("custome value");
 8    } catch (InterruptedException e) {
 9        e.printStackTrace();
10    }
11    System.out.println("Thread End ");
12}).run();
13String value = funtureHasReturnLambda.get();
14System.out.println("Main Method End value is "+ value);

我们可以发现输出是新起线程的输出值,当然这是因为我们的异步方法设置了等待10秒,如果此时异步方法等待1秒,新起的线程等待10秒,那么输出的值就是异步方法中的值了。

1Main Method Is Invoking
2Begin Invoke getFuntureHasReturnLambda
3Thread Is Invoking 
4Thread End 
5Main Method End value is custome value

按顺序执行异步任务

如果有一个异步任务的完成需要依赖前一个异步任务的完成,那么该如何写呢?是调用get()方法获得返回值以后然后再执行吗?这样写有些麻烦,CompletableFuture为我们提供了方法来完成我们想要顺序执行一些异步任务的需求。thenApplythenAcceptthenRun这三个方法。这三个方法的区别就是。

方法名 是否可获得前一个任务的返回值 是否有返回值
thenApply 能获得
thenAccept 能获得
thenRun 不可获得

所以一般来说thenAcceptthenRun这两个方法在调用链的最末端使用。接下来我们用真实的例子感受一下。

 1//thenApply  可获取到前一个任务的返回值,也有返回值
 2CompletableFuture<String> seqFutureOne = CompletableFuture.supplyAsync(()-> "seqFutureOne");
 3CompletableFuture<String> seqFutureTwo = seqFutureOne.thenApply(name -> name + " seqFutureTwo");
 4System.out.println(seqFutureTwo.get());
 5
 6
 7//thenAccept  可获取到前一个任务的返回值,但是无返回值
 8CompletableFuture<Void> thenAccept = seqFutureOne
 9        .thenAccept(name -> System.out.println(name + "thenAccept"));
10System.out.println("-------------");
11System.out.println(thenAccept.get());
12
13//thenRun 获取不到前一个任务的返回值,也无返回值
14System.out.println("-------------");
15CompletableFuture<Void> thenRun = seqFutureOne.thenRun(() -> {
16    System.out.println("thenRun");
17});
18System.out.println(thenRun.get());

返回的信息如下

1seqFutureOne seqFutureTwo
2seqFutureOnethenAccept
3-------------
4null
5-------------
6thenRun
7null

thenApply和thenApplyAsync的区别

我们可以发现这三个方法都带有一个后缀为Async的方法,例如thenApplyAsync。那么带Async的方法和不带此后缀的方法有什么不同呢?我们就以thenApplythenApplyAsync两个方法进行对比,其他的和这个一样的。

这两个方法区别就在于谁去执行这个任务,如果使用thenApplyAsync,那么执行的线程是从ForkJoinPool.commonPool()中获取不同的线程进行执行,如果使用thenApply,如果supplyAsync方法执行速度特别快,那么thenApply任务就是主线程进行执行,如果执行特别慢的话就是和supplyAsync执行线程一样。接下来我们通过例子来看一下,使用sleep方法来反应supplyAsync执行速度的快慢。

 1//thenApply和thenApplyAsync的区别
 2System.out.println("-------------");
 3CompletableFuture<String> supplyAsyncWithSleep = CompletableFuture.supplyAsync(()->{
 4    try {
 5        Thread.sleep(10000);
 6    } catch (InterruptedException e) {
 7        e.printStackTrace();
 8    }
 9    return "supplyAsyncWithSleep Thread Id : " + Thread.currentThread();
10});
11CompletableFuture<String> thenApply = supplyAsyncWithSleep
12        .thenApply(name -> name + "------thenApply Thread Id : " + Thread.currentThread());
13CompletableFuture<String> thenApplyAsync = supplyAsyncWithSleep
14        .thenApplyAsync(name -> name + "------thenApplyAsync Thread Id : " + Thread.currentThread());
15System.out.println("Main Thread Id: "+ Thread.currentThread());
16System.out.println(thenApply.get());
17System.out.println(thenApplyAsync.get());
18System.out.println("-------------No Sleep");
19CompletableFuture<String> supplyAsyncNoSleep = CompletableFuture.supplyAsync(()->{
20    return "supplyAsyncNoSleep Thread Id : " + Thread.currentThread();
21});
22CompletableFuture<String> thenApplyNoSleep = supplyAsyncNoSleep
23        .thenApply(name -> name + "------thenApply Thread Id : " + Thread.currentThread());
24CompletableFuture<String> thenApplyAsyncNoSleep = supplyAsyncNoSleep
25        .thenApplyAsync(name -> name + "------thenApplyAsync Thread Id : " + Thread.currentThread());
26System.out.println("Main Thread Id: "+ Thread.currentThread());
27System.out.println(thenApplyNoSleep.get());
28System.out.println(thenApplyAsyncNoSleep.get());

我们可以看到输出为

1-------------
2Main Thread Id: Thread[main,5,main]
3supplyAsyncWithSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]------thenApply Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]
4supplyAsyncWithSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]------thenApplyAsync Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]
5-------------No Sleep
6Main Thread Id: Thread[main,5,main]
7supplyAsyncNoSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]------thenApply Thread Id : Thread[main,5,main]
8supplyAsyncNoSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]------thenApplyAsync Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]

可以看到supplyAsync方法执行速度慢的话thenApply方法执行线程和supplyAsync执行线程相同,如果supplyAsync方法执行速度快的话,那么thenApply方法执行线程和Main方法执行线程相同。

组合CompletableFuture

将两个CompletableFuture组合到一起有两个方法

  1. thenCompose():当第一个任务完成时才会执行第二个操作
  2. thenCombine():两个异步任务全部完成时才会执行某些操作

thenCompose() 用法

我们定义两个异步任务,假设第二个定时任务需要用到第一个定时任务的返回值。

1public static CompletableFuture<StringgetTastOne(){
2    return CompletableFuture.supplyAsync(()-> "topOne");
3}
4
5public static CompletableFuture<StringgetTastTwo(String s){
6    return CompletableFuture.supplyAsync(()-> s + "  topTwo");
7}

我们利用thenCompose()方法进行编写

1CompletableFuture<String> thenComposeComplet = getTastOne().thenCompose(s -> getTastTwo(s));
2System.out.println(thenComposeComplet.get());

输出就是

1topOne  topTwo

如果还记得前面的thenApply()方法的话,应该会想这个利用thenApply()方法也是能够实现类似的功能的。

1//thenApply
2CompletableFuture<CompletableFuture<String>> thenApply = getTastOne()
3        .thenApply(s -> getTastTwo(s));
4System.out.println(thenApply.get().get());

但是我们发现返回值是嵌套返回的一个类型,而想要获得最终的返回值需要调用两次get()

thenCombine() 用法

例如我们此时需要计算两个异步方法返回值的和。求和这个操作是必须是两个异步方法得出来值的情况下才能进行计算,因此我们可以用thenCombine()方法进行计算。

1CompletableFuture<Integer> thenComposeOne = CompletableFuture.supplyAsync(() -> 192);
2CompletableFuture<Integer> thenComposeTwo = CompletableFuture.supplyAsync(() -> 196);
3CompletableFuture<Integer> thenComposeCount = thenComposeOne
4        .thenCombine(thenComposeTwo, (s, y) -> s + y);
5System.out.println(thenComposeCount.get());

此时thenComposeOnethenComposeTwo都完成时才会调用传给thenCombine方法的回调函数。

组合多个CompletableFuture

在上面我们用thenCompose()thenCombine()两个方法将两个CompletableFuture组装起来,如果我们想要将任意数量的CompletableFuture组合起来呢?可以使用下面两个方法进行组合。

  • allOf():等待所有CompletableFuture完后以后才会运行回调函数
  • anyOf():只要其中一个CompletableFuture完成,那么就会执行回调函数。注意此时其他的任务也就不执行了。

接下来演示一下两个方法的用法

 1//allOf()
 2CompletableFuture<Integer> one = CompletableFuture.supplyAsync(() -> 1);
 3CompletableFuture<Integer> two = CompletableFuture.supplyAsync(() -> 2);
 4CompletableFuture<Integer> three = CompletableFuture.supplyAsync(() -> 3);
 5CompletableFuture<Integer> four = CompletableFuture.supplyAsync(() -> 4);
 6CompletableFuture<Integer> five = CompletableFuture.supplyAsync(() -> 5);
 7CompletableFuture<Integer> six = CompletableFuture.supplyAsync(() -> 6);
 8
 9CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(one, two, three, four, five, six);
10voidCompletableFuture.thenApply(v->{
11    return Stream.of(one,two,three,four, five, six)
12            .map(CompletableFuture::join)
13            .collect(Collectors.toList());
14}).thenAccept(System.out::println);
15
16CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
17    try {
18        Thread.sleep(1000);
19    } catch (Exception e) {
20
21    }
22    System.out.println("1");
23});

我们定义了6个CompletableFuture等待所有的CompletableFuture等待所有任务完成以后然后将其值输出。

anyOf()的用法

 1CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
 2try {
 3    Thread.sleep(1000);
 4} catch (Exception e) {
 5
 6}
 7System.out.println("voidCompletableFuture1");
 8});
 9
10CompletableFuture<Void> voidCompletableFutur2 = CompletableFuture.runAsync(() -> {
11try {
12    Thread.sleep(2000);
13} catch (Exception e) {
14
15}
16System.out.println("voidCompletableFutur2");
17});
18
19CompletableFuture<Void> voidCompletableFuture3 = CompletableFuture.runAsync(() -> {
20try {
21    Thread.sleep(3000);
22} catch (Exception e) {
23
24}
25System.out.println("voidCompletableFuture3");
26});
27
28CompletableFuture<Object> objectCompletableFuture = CompletableFuture
29    .anyOf(voidCompletableFuture1, voidCompletableFutur2, voidCompletableFuture3);
30objectCompletableFuture.get();

这里我们定义了3个CompletableFuture进行一些耗时的任务,此时第一个CompletableFuture会率先完成。打印结果如下。

1voidCompletableFuture1

异常处理

我们了解了CompletableFuture如何异步执行,如何组合不同的CompletableFuture,如何顺序执行CompletableFuture。那么接下来还有一个重要的一步,就是在执行异步任务时发生异常的话该怎么办。我们先写个例子。

1CompletableFuture.supplyAsync(()->{
2    //发生异常
3    int i = 10/0;
4    return "Success";
5}).thenRun(()-> System.out.println("thenRun"))
6.thenAccept(v -> System.out.println("thenAccept"));
7
8CompletableFuture.runAsync(()-> System.out.println("CompletableFuture.runAsync"));

执行结果为,我们发现只要执行链中有一个发生了异常,那么接下来的链条也就不执行了,但是主流程下的其他CompletableFuture还是会运行的。

1CompletableFuture.runAsync

exceptionally()

我们可以使用exceptionally进行异常的处理

 1//处理异常
 2
 3CompletableFuture<String> exceptionally = CompletableFuture.supplyAsync(() -> {
 4    //发生异常
 5    int i = 10 / 0;
 6    return "Success";
 7}).exceptionally(e -> {
 8    System.out.println(e);
 9    return "Exception has Handl";
10});
11System.out.println(exceptionally.get());

打印如下,可以发现其接收值是异常信息,也能够返回自定义返回值。

1java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
2Exception has Handl

handle()

调用handle()方法也能够捕捉到异常并且自定义返回值,他和exceptionally()方法不同一点是handle()方法无论发没发生异常都会被调用。例子如下

 1System.out.println("-------有异常-------");
 2CompletableFuture.supplyAsync(()->{
 3    //发生异常
 4    int i = 10/0;
 5    return "Success";
 6}).handle((response,e)->{
 7    System.out.println("Exception:" + e);
 8    System.out.println("Response:" + response);
 9    return response;
10});
11
12System.out.println("-------无异常-------");
13CompletableFuture.supplyAsync(()->{
14    return "Sucess";
15}).handle((response,e)->{
16    System.out.println("Exception:" + e);
17    System.out.println("Response:" + response);
18    return response;
19});

打印如下,我们可以看到在没有发生异常的时候handle()方法也被调用了

1-------有异常-------
2Exception:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
3Response:null
4-------无异常-------
5Exception:null
6Response:Sucess

源代码地址

参考文章

今天的文章Java8——异步编程分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22373.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注