CompletableFuture应用

概要

1.java8的CompletableFuture是没有提供超时处理的,而获取结果的get()和join()都是阻塞等待,所以通过ScheduledThreadPoolExecutor来实现计时器功能。java9以后有提供orTimeout()
2.异步方法(即带Async后缀的方法):可以选择是否传递线程池参数Executor运行在指定线程池中;当不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPool(CommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈)
3.父子任务要做线程池隔离,避免发生死锁

相关API

函数

CompletableFutrue提供的三个相关函数

Supplier<U>  // 生产者,没有入参,有返回结果
Consumer<T>  // 消费者,有入参,但是没有返回结果
Function<T,U>// 函数,有入参,又有返回结果

依赖关系

thenApply():把前面任务的执行结果,交给后面的Function
thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回(即第一个任务的输出是第二个任务的输入)

与关系

thenCombine():合并任务,有返回值
thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理(需要两个任务的结果)
runAfterBoth():两个任务都执行完成后,执行下一步操作(不需要两个任务的结果)
allOf():需要所有给定的 CompletableFuture 完成
anyOf():只需任意一个给定的CompletablFuture完成

CompletableFuture<Void> future = future1.runAfterBoth(future2, () -> {
    // 执行操作,不关心结果
});

CompletableFuture<Void> future = future1.thenAcceptBoth(future2, (result1, result2) -> {
    // 使用 result1 和 result2 进行操作
});

或关系

applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
runAfterEither():任意一个任务执行完成,进行下一步操作

获取结果

get():获取结果,需要显式处理可能抛出的 InterruptedException 和 ExecutionException(try-catch)
join():获取结果,如果异步任务抛出了异常,join()会将异常封装为CompletionException并抛出。
无论是get()还是join()都是阻塞获取结果的

//不需要显式捕获异常,获得结果的结果可以知道
task.join()

//显式
try{
task.get()
}catch(InterruptedException | ExecutionException e){
//执行操作
}

异常

exceptionally():主要用于处理异常情况,当 CompletableFuture 异步计算过程中发生异常时会调用 exceptionally() 方法
handle():用于处理正常结果和异常情况,它可以处理计算过程中的异常,也可以处理正常的计算结果
whenComplete():会在计算完成时执行,可以访问计算结果和异常信息,没有返回值。只是执行一些任务完成时需要的操作

应用

多元依赖(只等待任务完成,不做处理)

@LogAround("测试异步任务并行")
    @GetMapping("/testA")
    public void testA() {
        ExecutorService executor = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));

        CompletableFuture<Integer> futureTask1 = supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            Integer result = dashboardService.getAccessTotalNumber();
            return result;
        }, executor);

        ScheduledThreadPoolExecutor timeOutExecutor = new ScheduledThreadPoolExecutor(1);
        CompletableFuture<Integer> futureTask2 = supplyAsync(() -> dashboardService.getUrlTotalNumber(), executor);
        CompletableFuture<Integer> futureTask3 = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor);
        CompletableFuture<Integer> futureTask4 = supplyAsync(() -> dashboardService.getAccessTodayNumber(), executor);

        CompletableFuture<Void> allTask = allOf(futureTask1,futureTask2, futureTask3, futureTask4)
                .applyToEither(TimeOutUtil.timeOut(timeOutExecutor, 0, TimeUnit.SECONDS), result -> result);

        allTask.join();

    }

超时处理工具类

package com.bluewind.shorturl.test.util;

import org.apache.poi.ss.formula.functions.T;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TimeOutUtil {
    public static CompletableFuture timeOut(ScheduledThreadPoolExecutor threadPoolExecutor, long timeout, TimeUnit unit) {
        //ScheduledThreadPoolExecutor是在指定时间后执行任务
        CompletableFuture result = new CompletableFuture();
        threadPoolExecutor.schedule(() -> {
                    System.out.println("========异步任务计时==========");
                    result.completeExceptionally(new Exception("异步任务执行超时了"));
                }, timeout, unit);

        return result;
    }
}

调用allOf()是没有相关的函数可以获取全部任务的结果,需要通过get()和join()自己获取。并且allOf()返回值为CompletableFutrue<Void>,所以如果想要对所有任务的结果进行处理并且返回CompletableFutrue<T>需要新建一个相关对象来做后续处理,例子里写好了。如果不要求任务异步执行,可以看下面同步执行,用上一个结果的输入作为下一个结果的输出,但是同样的,不能多个任务,限制了只能两个任务。

多元依赖(等待任务完成,并对结果处理)

@LogAround("测试异步任务")
    @GetMapping("/testB")
    public void testB() {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        CompletableFuture<Integer> futureTask1 = supplyAsync(() -> {
            //做其它事
            Integer result = dashboardService.getAccessTotalNumber();
            return result;
        }, executor);

        CompletableFuture<Integer> futureTask2 = supplyAsync(() -> dashboardService.getUrlTotalNumber(), executor);
        CompletableFuture<Integer> futureTask3 = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor);
        CompletableFuture<Integer> futureTask5 = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor);

        CompletableFuture<Void> allTask = allOf(futureTask1, futureTask2, futureTask3);
        CompletableFuture<Integer> futureTask4 = allTask.thenApplyAsync(v -> {
            //做其它事(这里join一定不会阻塞,因为上面allOf了)
            int result1 = futureTask1.join();
            int result2 = futureTask2.join();
            int result3 = futureTask3.join();
            if (result1 < 0) {
                throw new RuntimeException("查询出错");
            }
            return dashboardService.getAccessTodayNumber();
        }, executor).exceptionally(ex -> {
            Throwable t = ex.getCause();    //真正的异常封装在Throwable的cause属性里面
            t.printStackTrace();
            throw new RuntimeException(t);
        });

        Integer reuslt = futureTask4.join();

    }

父子任务(线程池隔离、get()显式捕获异常)

@LogAround("测试异步任务父子任务")
    @GetMapping("/testC")
    public void testC() {
        ExecutorService executor1 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
        ExecutorService executor2 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));

        CompletableFuture<String> futureTask = supplyAsync(() -> {
            //父任务
            String sParent = String.valueOf(dashboardService.getAccessTotalNumber());

            //子任务
            CompletableFuture<Integer> futureTaskChild = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor2);
            String sChild = String.valueOf(futureTaskChild.join());

            return sParent + sChild;
        }, executor1);

        try {
            futureTask.get(10, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException e) {
            Throwable t = e.getCause();   //真正的异常封装在Throwable的cause属性里面
            t.printStackTrace();
        } catch (TimeoutException e) {
            throw new RuntimeException(e);
        }
    }

上述例子中,在父任务里面还有一个子任务。这里要做线程池隔离。如果用同一个线程池,可能会出现线程池循环引用导致死锁(父任务获取到线程后,等到子任务获得线程完成执行,但是此时线程池中没有多余的线程,导致父任务得不到执行,如果此时主线程调用get()或者join()阻塞等待结果并且没有对执行时间做任何处理,主线程将会被阻塞在这里。
还要注意一点,CompletableFuture在回调方法中对异常进行了包装,如果要获取真正的异常,应该通过Throwable.getCause()获取

链式调用

@LogAround("链式执行")
    @GetMapping("/testD")
    public void testD() {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        CompletableFuture<Integer> futureTask1 = supplyAsync(() -> {
            //做其它事
            Integer result = dashboardService.getAccessTotalNumber();
            return result;
        }, executor);

        CompletableFuture<Integer> futureTask2 = supplyAsync(() -> dashboardService.getUrlTotalNumber(), executor);
        CompletableFuture<Integer> futureTask3 = supplyAsync(() -> dashboardService.getAccessTodayIpNumber(), executor);

        CompletableFuture<Integer> allTask = futureTask1.thenCombineAsync(futureTask2,(reslut1,result2)->{
            //执行操作,这里的return是给下一个thenCombine的
            return "true";
        },executor).thenCombineAsync(futureTask3,(result2, result3)->{
            //执行操作,这里的return是最终返回值,要和CompletableFuture<T>类型匹配
            return 1;
        },executor).exceptionally(ex -> {
            Throwable t = ex.getCause();    //真正的异常封装在Throwable的cause属性里面
            t.printStackTrace();
            throw new RuntimeException(t);
        });;

        Integer allReuslt = allTask.join();

    }

一个thenCombineAsync只能接一个任务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值