CompletableFuture异步处理

一、异步执行

1、runAsync

功能:基于runAsync系列方法实现无返回值的异步计算

场景:当你想异步执行一个任务,并且不需要任务的执行结果时可以使用该方法,比如异步打日志,异步做消息通知等

/**
 * 基于runAsync系列方法实现无返回值的异步计算
 * @throws ExecutionException
 * @throws InterruptedException
 */
@Test
public void testRunAsync() throws ExecutionException, InterruptedException {
    // 1、创建异步任务,并返回future
    CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {

        @Override
        public void run() {
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("执行完成");
        }
    });

    // 2、同步等待异步任务执行结果,此处是阻塞等待
    future.get();
}

说明:为什么没有用java8的函数式编程,主要是为了让大家更加清楚的知道使用,真实场景下可以改成函数式编程方式,代码会更加简洁

调用返回的future的get()方法企图等待future任务执行完毕,由于runAsync方法不会有返回值,并且从future中我们也可以看到泛型Void,所以当任务执行完毕后,设置future的结果为null,即代码2处等任务执行完毕后返回null

需要注意的是,在默认情况下,runAsync(Runnable runnable) 方法是使用整个JVM内唯一的 ForkJoinPool.commonPool() 线程池来执行异步任务的,使用runAsync (Runnable runnable, Executor executor)方法允许我们使用自己制定的线程池来执行异步任务

2、supplyAsync

功能:基于supplyAsync系列方法实现有返回值的异步计算

场景:当你想异步执行一个任务,并且需要任务的执行结果时可以使用该方法,比如异步对原始数据进行加工,并需要获取到被加工后的结果等

/**
 * 基于supplyAsync系列方法实现有返回值的异步计算
 * @throws ExecutionException
 * @throws InterruptedException
 */
@Test
public void testSupplyAsync() throws ExecutionException, InterruptedException {
    // 1、创建异步任务,并返回future
    CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {

        @Override
        public String get() {
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello, 你好!";
        }

    });

    // 2、同步等待异步任务执行结果,此处是阻塞等待
    String result = future.get();
    System.out.println(result);
}

2处使用future的get()方法获取结果,一开始future结果并没有被设置,所以调用线程会被阻塞;等异步任务把结果设置到future后,调用线程就会从get()处返回异步任务执行的结果

需要注意的是,在默认情况下,supplyAsync(Supplier supplier)方法是使用整个JVM内唯一的ForkJoinPool.commonPool()线程池来执行异步任务的,使用supplyAsync(Suppliersupplier, Executor executor)方法允许我们使用自己制定的线程池来执行异步任务

二、future 执行完之后添加事件处理

1、thenRun

基于thenRun实现异步任务A,执行完毕后,激活异步任务B执行,需要注意的是,这种方式激活的异步任务B是拿不到任务A的执行结果的

/**
 * A 执行完成之后通知 B开始执行,B无法获取到A的结果
 * @throws ExecutionException
 * @throws InterruptedException
 */
@Test
public void testThenRun() throws ExecutionException, InterruptedException {

    // 创建异步任务,返回future  one
    CompletableFuture<String> one = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("one thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "我是 one 的结果";
        }
    });

    // 在one 的基础上添加事件,当one执行完成之后调用该事件,并返回新的future two
    CompletableFuture<Void> two = one.thenRun(new Runnable() {
        @Override
        public void run() {
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("two thread name : " + Thread.currentThread().getName() + " 正在执行");
        }
    });

    // 同步等待two的执行完成,整个执行链为,先one -> two -> 返回
    System.out.println(two.get());
}
one thread name : ForkJoinPool.commonPool-worker-9 正在执行
two thread name : ForkJoinPool.commonPool-worker-9 正在执行
null

默认情况下oneFuture对应的异步任务和在oneFuture上添加的回调事件都是使用ForkJoinPool.commonPool()中的同一个线程来执行的,大家可以使用thenRunAsync (Runnableaction, Executor executor)来指定设置的回调事件使用自定义线程池线程来执行,也就是oneFuture对应的任务与在其上设置的回调执行将不会在同一个线程中执行

2、thenAccept

基于thenAccept实现异步任务A,执行完毕后,激活异步任务B执行,需要注意的是,这种方式激活的异步任务B是可以拿到任务A的执行结果的

/**
 * A 执行完成之后通知 B开始执行,A 的结果会传递给 B,但是B执行之后没有结果返回
 * @throws ExecutionException
 * @throws InterruptedException
 */
@Test
public void testThenAccept() throws ExecutionException, InterruptedException {
    // 创建异步任务,返回future  one
    CompletableFuture<String> one = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("one thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "我是 one 的结果";
        }
    });

    // 在one 的基础上添加事件,当one执行完成之后调用该事件,并返回新的future two
    CompletableFuture<Void> two = one.thenAccept(new Consumer<String>() {

        /**
         * @param s one 执行的结果会传递过来
         */
        @Override
        public void accept(String s) {
            System.out.println("two任务重accept接收到了one执行完成后的结果 :" + s);
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("two thread name : " + Thread.currentThread().getName() + " 正在执行");
        }
    });

    // 同步等待two的执行完成,整个执行链为,先one -> two -> 返回
    System.out.println(two.get());
}

运行结果:

one thread name : ForkJoinPool.commonPool-worker-9 正在执行
two任务重accept接收到了one执行完成后的结果 :我是 one 的结果
two thread name : ForkJoinPool.commonPool-worker-9 正在执行
null

这里可以在回调的方法accept(String s)的参数t中来获取oneFuture对应的任务结果,另外需要注意的是,由于accept(String s)方法没有返回值,所以在twoFuture上调用get()方法最终也会返回null

在默认情况下,oneFuture对应的异步任务和在oneFuture上添加的回调事件都是使用ForkJoinPool.commonPool()中的同一个线程来执行的,大家可以使用thenAcceptAsync(Consumer<? super T> action, Executor executor)来指定设置的回调事件使用自定义线程池线程来执行,也就是oneFuture对应的任务与在其上设置的回调执行将不会在同一个线程中执行

3、thenApply

基于thenApply实现异步任务A,执行完毕后,激活异步任务B执行。需要注意的是,这种方式激活的异步任务B是可以拿到任务A的执行结果的,并且可以获取到异步任务B的执行结果

/**
 * A 执行完成之后通知 B开始执行,A 的结果会传递给 B,B也返回执行结果数据
 */
@Test
public void testThenApply() throws ExecutionException, InterruptedException {
    // 创建异步任务,返回future  one
    CompletableFuture<String> one = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("one thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "我是 one 的结果";
        }
    });

    // 在one 的基础上添加事件,当one执行完成之后调用该事件,并返回新的future two
    CompletableFuture<String> two = one.thenApply(new Function<String, String>() {
        @Override
        public String apply(String s) {
            System.out.println("two任务重accept接收到了one执行完成后的结果 :" + s);
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("two thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "我是 two 的结果";
        }
    });

    // 同步等待two的执行完成,整个执行链为,先one -> two -> 返回结果
    System.out.println(two.get());
}

执行结果:

one thread name : ForkJoinPool.commonPool-worker-9 正在执行
two任务重accept接收到了one执行完成后的结果 :我是 one 的结果
two thread name : ForkJoinPool.commonPool-worker-9 正在执行
我是 two 的结果

需要注意的是,这里可以在回调方法apply(String s)的参数t中获取oneFuture对应的任务结果,另外需要注意的是,由于apply(String s)方法有返回值,所以在twoFuture上调用get()方法最终也会返回回调方法返回的值。

默认情况下oneFuture对应的异步任务和在oneFuture上添加的回调事件都是使用ForkJoinPool.commonPool()中的同一个线程来执行的,大家可以使用thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor)来指定设置的回调事件使用自定义线程池线程来执行,也就是oneFuture对应的任务与在其上设置的回调执行将不会在同一个线程中执行

4、whenComplete

基于whenComplete设置回调函数,当异步任务执行完毕后进行回调,不会阻塞调用线程

/**
 * 基于whenComplete设置回调函数,当异步任务执行完毕后进行回调,不会阻塞调用线程
 */
@Test
public void testWhenComplete() throws InterruptedException {
    // 1、创建异步任务,返回future  one
    CompletableFuture<String> one = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            // 休眠2秒模拟计算任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("one thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "我是 one 的结果";
        }
    });

    // 2、添加回调函数
    one.whenComplete(new BiConsumer<String, Throwable>() {
        @Override
        public void accept(String s, Throwable throwable) {
            if (throwable == null) {
                System.out.println("回调,异步执行结果入参:" + s);
            } else {
                System.out.println("回调,异步执行出现异常:" + throwable.getLocalizedMessage());
            }
        }
    });

    ///3、挂起主线程,等待上面异步线程执行完
    System.out.println("主线后续任务");
    Thread.currentThread().join();
}

执行结果:

主线后续任务
one thread name : ForkJoinPool.commonPool-worker-9 正在执行
回调,异步执行结果入参:我是 one 的结果

代码2则在返回的future上调用whenComplete设置一个回调函数,然后main线程就返回了。在整个异步任务的执行过程中,main函数所在线程是不会被阻塞的,等异步任务执行完毕后会回调设置的回调函数,在回调函数内,代码2.1表示如果发现异步任务执行正常则打印执行结果,否则打印异常信息。这里代码3挂起了main函数所在线程,是因为具体执行异步任务的是ForkJoin的commonPool线程池,其中线程都是Deamon线程,所以,当唯一的用户线程main线程退出后整个JVM进程就退出了,会导致异步任务得不到执行

如上所述,当我们使用CompletableFuture实现异步编程时,大多数时候是不需要显式创建线程池,并投递任务到线程池内的。我们只需要简单地调用CompletableFuture的runAsync或者supplyAsync等方法把异步任务作为参数即可,其内部会使用ForkJoinPool线程池来进行异步执行的支持,这大大简化了我们异步编程的负担,实现了声明式编程(告诉程序我要执行异步任务,但是具体怎么实现我不需要管),当然如果你想使用自己的线程池来执行任务,也是可以非常方便地进行设置的

三、 多个CompletableFuture进行组合运算

1、thenCompose

基于thenCompose实现当一个CompletableFuture执行完毕后,执行另外一个CompletableFuture

/**
 * 基于thenCompose实现当一个CompletableFuture执行完毕后,执行另外一个CompletableFuture
 * @throws ExecutionException
 * @throws InterruptedException
 */
@Test
public void testThenCompose() throws ExecutionException, InterruptedException {
    CompletableFuture<String> one = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println("one thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "value1";
        }
    });


    CompletableFuture<String> two = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println("two thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "value2";
        }
    });

    CompletableFuture result = one.thenCompose(r -> two);
    System.out.println(result.get());
}

执行结果:

one thread name : ForkJoinPool.commonPool-worker-9 正在执行
two thread name : ForkJoinPool.commonPool-worker-9 正在执行
value2

2、thenCombine

基于thenCombine实现当两个并发运行的CompletableFuture任务都完成后,使用两者的结果作为参数再执行一个异步任务

/**
 * 基于thenCombine实现当两个并发运行的CompletableFuture任务都完成后,使用两者的结果作为参数再执行一个异步任务
 * @throws ExecutionException
 * @throws InterruptedException
 */
@Test
public void testThenCombine() throws ExecutionException, InterruptedException {
    CompletableFuture<String> one = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println("one thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "value1";
        }
    });


    CompletableFuture<String> two = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println("two thread name : " + Thread.currentThread().getName() + " 正在执行");
            return "value2";
        }
    });

    CompletableFuture<String> result = one.thenCombine(two, (r1, r2) -> r1 + r2);
    System.out.println(result.get());
}

运行结果:

one thread name : ForkJoinPool.commonPool-worker-9 正在执行
two thread name : ForkJoinPool.commonPool-worker-9 正在执行
value1value2

3、allOf

基于 allOf 等待多个并发运行的CompletableFuture任务执行完毕

/**
  * 基于allOf等待多个并发运行的CompletableFuture任务执行完毕
  * @throws ExecutionException
  * @throws InterruptedException
  */
@Test
public void testAllOf() throws ExecutionException, InterruptedException {
    // 向容器中添加5个 CompletableFuture
    CompletableFuture<String>[] futures = new CompletableFuture[5];

    for (int i = 0; i < futures.length; i++) {
        int count = i;

        futures[i] = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                System.out.println("thread name : " + Thread.currentThread().getName() + " 正在执行");
                return "value" + count;
            }
        });
    }

    System.out.println("开始执行");

    // 执行容器中的所有 future
    CompletableFuture<Void> result = CompletableFuture.allOf(futures);

    // 阻塞等待所有future执行完成
    System.out.println(result.get());

    System.out.println("执行结束");
}

执行结果:

开始执行
thread name : ForkJoinPool.commonPool-worker-9 执行完成
thread name : ForkJoinPool.commonPool-worker-2 执行完成
thread name : ForkJoinPool.commonPool-worker-2 执行完成
thread name : ForkJoinPool.commonPool-worker-9 执行完成
thread name : ForkJoinPool.commonPool-worker-11 执行完成
null
执行结束

在result上调用get()方法会阻塞调用线程,直到futureList列表中所有任务执行完毕才返回

4、anyOf

基于anyOf 等多个并发运行的CompletableFuture任务中有一个执行完毕就返回

/**
 * 基于anyOf 等多个并发运行的CompletableFuture任务中有一个执行完毕就返回
 * @throws ExecutionException
 * @throws InterruptedException
 */
@Test
public void testAnyOf() throws ExecutionException, InterruptedException {
    // 向容器中添加5个 CompletableFuture
    CompletableFuture<String>[] futures = new CompletableFuture[5];

    for (int i = 0; i < futures.length; i++) {
        int count = i;

        futures[i] = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                System.out.println(count + "thread name : " + Thread.currentThread().getName() + " 正在执行");
                return "value" + count;
            }
        });
    }

    System.out.println("开始执行");

    // 执行容器中的所有 future
    CompletableFuture<Object> result = CompletableFuture.anyOf(futures);

    // 阻塞等待所有future执行完成
    System.out.println(result.get());

    System.out.println("执行结束");
}

执行结果:

开始执行
0thread name : ForkJoinPool.commonPool-worker-9 正在执行
1thread name : ForkJoinPool.commonPool-worker-2 正在执行
4thread name : ForkJoinPool.commonPool-worker-9 正在执行
value0
执行结束
2thread name : ForkJoinPool.commonPool-worker-11 正在执行
3thread name : ForkJoinPool.commonPool-worker-2 正在执行

在result上调用get()方法会阻塞调用线程,直到futureList列表中有一个任务执行完毕才返回

四、异常处理

前面的代码为我们演示的功能都是当异步任务内可以正常设置任务结果时的情况,但是情况并不总是这样的

1、先看一个错误实例

@Test
public void testException() throws ExecutionException, InterruptedException {
    // 1、构建future对象
    CompletableFuture<String> future = new CompletableFuture<>();

    new Thread(() -> {
        try {
            // 2、主动抛出异常
            if (true) {
                throw new RuntimeException("出现异常");
            }

            // 3、设置正常执行结果
            future.complete("ok");
        } catch (RuntimeException e) {
            e.printStackTrace();
        }

        System.out.println("------ " + Thread.currentThread().getName() + " ------- set future result");
    }, "thread-1").start();

    // 4、获取执行结果
    System.out.println(future.get());

    // 5、后续任务
    System.out.println("执行结束");
}

执行结果:

java.lang.RuntimeException: 出现异常
	at completablefuture.CompletableFutureException.lambda$testException$0(CompletableFutureException.java:31)
	at java.lang.Thread.run(Thread.java:745)
------ thread-1 ------- set future result

我们发现代码 2 处会主动抛出一个异常,那么3处的赋值操作永远是得不到执行,那么就会导致 4处 永远获取不到结果,一直阻塞不放。5处的代码永远也得不到执行,线程一直卡在4处也不会终止

在这里插入图片描述

2、completeExceptionally

所以我们不仅需要考虑正常设置结果的情况,还需要考虑异常的情况,其实CompletableFuture提供了completeExceptionally方法来处理异常情况

/**
 * completeExceptionally 进行异常处理
 * @throws ExecutionException
 * @throws InterruptedException
 */
@Test
public void testCompleteExceptionally() throws ExecutionException, InterruptedException {
    // 1、构建future对象
    CompletableFuture<String> future = new CompletableFuture<>();

    new Thread(() -> {
        try {
            // 2、主动抛出异常
            if (true) {
                throw new RuntimeException("出现异常");
            }

            // 3、设置正常执行结果
            future.complete("ok");
        } catch (RuntimeException e) {
            e.printStackTrace();
            // 4、出现异常,将异常传递给 future
            future.completeExceptionally(e);
        }

        System.out.println("------ " + Thread.currentThread().getName() + " ------- set future result");
    }, "thread-1").start();

    // 5、future 设置出现异常时,返回默认值
    CompletableFuture<String> f = future.exceptionally(e -> "default");

    // 6、等待执行结果
    System.out.println(f.get());

    // 7、后续任务
    System.out.println("执行结束");
}

执行结果:

java.lang.RuntimeException: 出现异常
	at completablefuture.CompletableFutureException.lambda$testCompleteExceptionally$1(CompletableFutureException.java:59)
	at java.lang.Thread.run(Thread.java:745)
------ thread-1 ------- set future result
default
执行结束

多了 4 处的代码,将异常传递给 future 对象。还有一点要注意的是,如果没有 5 处设置默认值的操作,那么6处执行也会获取不到结果,7 处后续任务也不会执行,但是线程不会卡住,在6处就会结束掉了

参考:《java异步编程实战》

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值