异步编程 - 05 基于JDK中的Future实现异步编程(中)_CompletableFuture

news2025/1/10 11:15:09

文章目录

  • Pre
  • 概述
  • 显式设置CompletableFuture结果
  • 基于CompletableFuture实现异步计算与结果转换
    • 1)基于runAsync系列方法实现无返回值的异步计算
    • 2)基于supplyAsync系列方法实现有返回值的异步计算
    • 3)基于thenRun实现异步任务A,执行完毕后,激活异步任务B执行 (需要注意的是,这种方式激活的异步任务B是拿不到任务A的执行结果的)
    • 4)基于thenAccept实现异步任务A,执行完毕后,激活异步任务B执行 (需要注意的是,这种方式激活的异步任务B是可以拿到任务A的执行结果的)
    • 5)基于thenApply实现异步任务A,执行完毕后,激活异步任务B执行 (需要注意的是,这种方式激活的异步任务B是可以拿到任务A的执行结果的,并且可以获取到异步任务B的执行结果)
    • 6)基于whenComplete设置回调函数,当异步任务执行完毕后进行回调,不会阻塞调用线程
    • 小结
  • 多个CompletableFuture进行组合运算
    • 1)基于thenCompose实现当一个CompletableFuture执行完毕后,执行另外一个CompletableFuture:
    • 2)基于thenCombine实现当两个并发运行的CompletableFuture任务都完成后,使用两者的结果作为参数再执行一个异步任务,这里只需要把上面例子中的:
    • 3)基于allOf等待多个并发运行的CompletableFuture任务执行完毕:
    • 4)基于anyOf等多个并发运行的CompletableFuture任务中有一个执行完毕就返回
  • 异常处理

在这里插入图片描述


Pre

并发编程 - CompletableFuture

Java8 - 使用CompletableFuture 构建异步应用

Java8 - 使用工厂方法 supplyAsync创建 CompletableFuture

每日一博 - Java 异步编程的 Promise 模式 CompletableFuture的前世今生 (上)

Java8 - 自定义实现体会CompletableFuture的原理

Java 8 - CompletableFuture组合式异步编程


概述

CompletableFuture是一个可以通过编程方式显式地设置计算结果和状态以便让任务结束的Future,并且其可以作为一个CompletionStage(计算阶段),当它的计算完成时可以触发一个函数或者行为;当多个线程企图调用同一个CompletableFuture的complete、cancel方式时只有一个线程会成功。

CompletableFuture除了含有可以直接操作任务状态和结果的方法外,还实现了CompletionStage接口的一些方法,这些方法遵循:

  • 当CompletableFuture任务完成后,同步使用任务执行线程来执行依赖任务结果的函数或者行为。

  • 所有异步的方法在没有显式指定Executor参数的情形下都是复用ForkJoinPool.commonPool()线程池来执行。

  • 所有CompletionStage方法的实现都是相互独立的,以便一个方法的行为不会因为重载了其他方法而受影响。

一个CompletableFuture任务可能有一些依赖其计算结果的行为方法,这些行为方法被收集到一个无锁基于CAS操作来链接起来的链表组成的栈中;当Completable-Future的计算任务完成后,会自动弹出栈中的行为方法并执行。

需要注意的是,由于是栈结构,在同一个CompletableFuture对象上行为注册的顺序与行为执行的顺序是相反的。

由于默认情况下支撑CompletableFuture异步运行的是ForkJoinPool

所以这里我们有必要简单讲解下ForkJoinPool。ForkJoinPool本身也是一种ExecutorService,与其他ExecutorService(比如ThreadPoolExecutor)相比,不同点是它使用了工作窃取算法来提高性能,其内部每个工作线程都关联自己的内存队列,正常情况下每个线程从自己队列里面获取任务并执行,当本身队列没有任务时,当前线程会去其他线程关联的队列里面获取任务来执行。这在很多任务会产生子任务或者有很多小的任务被提交到线程池来执行的情况下非常高效。

ForkJoinPool中有一个静态的线程池commonPool可用且适用大多数情况。commonPool会被任何未显式提交到指定线程池的ForkJoinTask执行使用。使用commonPool通常会减少资源使用(其线程数量会在不活跃时缓慢回收,并在任务数比较多的时候按需增加)。默认情况下,commonPool的参数可以通过system properties中的三个参数来控制:

java.util.concurrent.ForkJoinPool.common.parallelism:并行度级别,非负整数。

java.util.concurrent.ForkJoinPool.common.threadFactory:ForkJoinWorker ThreadFactory的类名。

java.util.concurrent.ForkJoinPool.common.exceptionHandler:Uncaught ExceptionHandler的类名。

对于需要根据不同业务对线程池进行隔离或者定制的情况,可以使用ForkJoinPool的构造函数显式设置线程个数,默认情况下线程个数等于当前机器上可用的CPU个数

ForkJoinPool中提供了任务执行、任务生命周期控制的方法,还提供了任务状态监测的方法,比如getStealCount可以帮助调整和监控fork/join应用程序。另外,toSring方法会非常方便地返回当前线程池的状态(运行状态、线程池线程个数、激活线程个数、队列中任务个数)。

另外,当线程池关闭或者内部资源被耗尽(比如当某个队列大小大于67108864时),再向线程池提交任务会抛出RejectedExecutionException异常。

显式设置CompletableFuture结果

CompletableFuture是一种可以通过编程显式设置结果的future,下面我们通过一个例子来演示下:

public class TestCompletableFutureSet {
    // 0自定义线程池
    private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
    private final static ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
            AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(5),
            new ThreadPoolExecutor.CallerRunsPolicy());

    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

        // 1.创建一个CompletableFuture对象
        CompletableFuture<String> future = new CompletableFuture<String>();

        // 2.开启线程计算任务结果,并设置
        POOL_EXECUTOR.execute(() -> {

            // 2.1休眠3s,模拟任务计算
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 2.2设置计算结果到future
            System.out.println("----" + Thread.currentThread().getName() + " set future result----");
            future.complete("hello,artisan");

        });

        // 3.等待计算结果
        System.out.println("---main thread wait future result---");
        System.out.println(future.get());
        // System.out.println(future.get(1000,TimeUnit.MILLISECONDS));
        System.out.println("---main thread got future result---");
    }
}
  • 由上述代码可知,代码0创建了一个线程池,代码1创建了一个CompletableFuture对象,代码2将提交任务到异步线程池中执行。

  • 代码3调用future的get()方法企图获取future的结果,如果future的结果没有被设置,则调用线程会被阻塞。

  • 在代码2创建的任务内,代码2.1表示休眠3s,模拟异步任务的执行,代码2.2则表示在休眠3s后,调用future的complete方法设置future的结果,设置完结果后,所有由于调用future的get()方法而被阻塞的线程会被激活,并返回设置的结果。

如上所述,这里使用CompletableFuture实现了通知等待模型,主线程调用future的get()方法等待future返回结果,一开始由于future结果没有设置,所以主线程被阻塞挂起,等异步任务休眠3s,然后调用future的complete方法模拟主线程等待的条件完成,这时候主线程就会从get()方法返回。


基于CompletableFuture实现异步计算与结果转换

1)基于runAsync系列方法实现无返回值的异步计算

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

public static void runAsync() throws InterruptedException, ExecutionException {
        // 1.1创建异步任务,并返回future
        CompletableFuture future = CompletableFuture.runAsync(new Runnable() {

            @Override
            public void run() {
                // 1.1.1休眠2s模拟任务计算
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("over");
            }
        });

        // 1.2 同步等待异步任务执行结束
        System.out.println(future.get());
    }
  • 代码1.1创建了一个异步任务,并马上返回一个future对象,其创建了一个异步任务执行,任务内首先休眠2s,然后打印了一行日志。

  • 代码1.2则调用返回的future的get()方法企图等待future任务执行完毕,由于runAsync方法不会有返回值,所以当任务执行完毕后,设置future的结果为null,即代码1.2等任务执行完毕后返回null。

需要注意的是,在默认情况下,runAsync(Runnable runnable)方法是使用整个JVM内唯一的ForkJoinPool.commonPool()线程池来执行异步任务的,使用runAsync(Runnable runnable,Executor executor)方法允许我们使用自己制定的线程池来执行异步任务。我们创建了一个自己的线程池bizPoolExecutor,在调用runAsync方法提交异步任务时,把其作为第二参数进行传递,则异步任务执行时会使用bizPoolExecutor中的线程执行,具体代码如下所示。

// 0.创建线程池
private static final ThreadPoolExecutor bizPoolExecutor = new ThreadPoolExecutor(8, 8, 1, TimeUnit.MINUTES,
        new LinkedBlockingQueue<>(10));

//没有返回值的异步执行,异步任务由业务自己的线程池执行
public static void runAsyncWithBizExecutor() throws InterruptedException, ExecutionException {
    // 1.1创建异步任务,并返回future
    CompletableFuture future = CompletableFuture.runAsync(new Runnable() {

        @Override
        public void run() {
            // 1.1.1休眠2s模拟任务计算
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("over");
        }
    }, bizPoolExecutor);

    // 1.2 同步等待异步任务执行结束
    System.out.println(future.get());
}

2)基于supplyAsync系列方法实现有返回值的异步计算

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

// 2. 有返回值的异步执行
public static void supplyAsync() throws InterruptedException, ExecutionException {
    // 2.1创建异步任务,并返回future
    CompletableFuture future = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            // 2.1.1休眠2s模拟任务计算
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 2.1.2 返回异步计算结果
            return "hello,jiaduo";
        }
    });

    // 2.2 同步等待异步任务执行结束
    System.out.println(future.get());
}
  • 代码2.1使用supplyAsync开启了一个异步任务,执行后马上返回一个future对象;异步任务内线程休眠2s,然后返回了一个字符串结果,这个结果会被设置到future内部。

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

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

// 0.创建线程池
private static final ThreadPoolExecutor bizPoolExecutor = new ThreadPoolExecutor(8, 8, 1, TimeUnit.MINUTES,
        new LinkedBlockingQueue<>(10));

// 2. 有返回值的异步执行
public static void supplyAsyncWithBizExecutor() throws InterruptedException, ExecutionException {
    // 2.1创建异步任务,并返回future
    CompletableFuture future = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            // 2.1.1休眠2s模拟任务计算
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 2.1.2 返回异步计算结果
            return "hello,jiaduo";
        }
    }, bizPoolExecutor);

    // 2.2 同步等待异步任务执行结束
    System.out.println(future.get());
}

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

需要注意的是,这种方式激活的异步任务B是拿不到任务A的执行结果的:

    // I thenRun不能访问oneFuture的结果
    public static void thenRun() throws InterruptedException, ExecutionException {
        // 1.创建异步任务,并返回future
        CompletableFuture<String> oneFuture = CompletableFuture.supplyAsync(new Supplier<String>() {

            @Override
            public String get() {
                // 1.1休眠2s,模拟任务计算
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 1.2返回计算结果
                return "hello";
            }
        });
        // 2.在future上施加事件,当future计算完成后回调该事件,并返回新future
        CompletableFuture twoFuture = oneFuture.thenRun(new Runnable() {

            @Override
            public void run() {
                // 2.1.1当oneFuture任务计算完成后做一件事情
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
                System.out.println("---after oneFuture over doSomething---");
            }
        });

        // 3.同步等待twoFuture对应的任务完成,返回结果固定为null
        System.out.println(twoFuture.get());

    }

由上述代码可知,代码1创建异步任务,并返回oneFuture对象,代码2在oneFuture上调用thenRun方法添加异步执行事件,当oneFuture计算完成后回调该事件,并返回twoFuture,另外,在twoFuture上调用get()方法也会返回null,因为回调事件是没有返回值的。

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


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

需要注意的是,这种方式激活的异步任务B是可以拿到任务A的执行结果的:

public static void thenAccept() throws InterruptedException, ExecutionException {
    // 1.创建异步任务,并返回future
    CompletableFuture<String> oneFuture = CompletableFuture.supplyAsync(new Supplier<String>() {

        @Override
        public String get() {

            // 1.1休眠2s,模拟任务计算
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 1.2返回计算结果
            return "hello";
        }
    });
    // 2.在future上施加事件,当future计算完成后回调该事件,并返回新future
    CompletableFuture twoFuture = oneFuture.thenAccept(new Consumer<String>() {

        @Override
        public void accept(String t) {
            // 2.1.1对oneFuture返回的结果进行加工
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }

            System.out.println("---after oneFuture over doSomething---" + t);
        }
    });

    // 3.同步等待twoFuture对应的任务完成,返回结果固定为null
    System.out.println(twoFuture.get());
}

在上述代码中,代码1创建异步任务,并返回oneFuture,代码2在oneFuture上调用thenAccept添加了一个任务,这个任务会在oneFuture对应的任务执行完毕后被激活执行。

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

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


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

需要注意的是,这种方式激活的异步任务B是可以拿到任务A的执行结果的,并且可以获取到异步任务B的执行结果

public class TestCompletableFutureCallBack {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 1.创建异步任务,并返回future
        CompletableFuture<String> oneFuture = CompletableFuture.supplyAsync(new Supplier<String>() {

            @Override
            public String get() {

                // 1.1休眠2s,模拟任务计算
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 1.2返回计算结果
                return "hello";
            }
        });

        // 2.在future上施加事件,当future计算完成后回调该事件,并返回新future
        CompletableFuture<String> twoFuture = oneFuture.thenApply(new Function<String, String>() {

            // 2.1在步骤1计算结果基础上进行计算,这里t为步骤1返回的hello
            @Override
            public String apply(String t) {
                // 2.1.1对oneFuture返回的结果进行加工
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                // 2.1.2返回加工后结果
                return t + " artisan";
            }
        });

        // 3.同步等待twoFuture对应的任务完成,并获取结果
        System.out.println(twoFuture.get());

    }
}

在上述代码中,代码1创建异步任务,并返回oneFuture,代码2在oneFuture上调用thenApply添加了一个任务,这个任务会在oneFuture对应的任务执行完毕后被激活执行。需要注意的是,这里可以在回调方法apply(String t)的参数t中获取oneFuture对应的任务结果,另外需要注意的是,由于apply(String t)方法有返回值,所以在twoFuture上调用get()方法最终也会返回回调方法返回的值。

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


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

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

    // 1.创建一个CompletableFuture对象
    CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {

        @Override
        public String get() {
            // 1.1模拟异步任务执行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 1.2返回计算结果
            return "hello,jiaduo";
        }
    });

    // 2.添加回调函数
    future.whenComplete(new BiConsumer<String, Throwable>() {

        @Override
        public void accept(String t, Throwable u) {
            // 2.1如果没有异常,打印异步任务结果
            if (null == u) {
                System.out.println(t);
            } else {
                // 2.2打印异常信息
                System.out.println(u.getLocalizedMessage());

            }
        }
    });

    // 3.挂起当前线程,等待异步任务执行完毕
    Thread.currentThread().join();
}

这里代码1开启了一个异步任务,任务内先休眠1s,然后代码1.2返回计算结果;代码2则在返回的future上调用whenComplete设置一个回调函数,然后main线程就返回了。

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


小结

如上所述,当我们使用CompletableFuture实现异步编程时,大多数时候是不需要显式创建线程池,并投递任务到线程池内的。

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


多个CompletableFuture进行组合运算

CompletableFuture功能强大的原因之一是其可以让两个或者多个Completable-Future进行运算来产生结果,下面我们来看其提供的几组函数:

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

public class TestTwoCompletableFuture {
    // 1.异步任务,返回future
    public static CompletableFuture<String> doSomethingOne(String encodedCompanyId) {
        // 1.1创建异步任务
        return CompletableFuture.supplyAsync(new Supplier<String>() {

            @Override
            public String get() {

                // 1.1.1休眠1s,模拟任务计算
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 1.1.2 解密,并返回结果
                String id = encodedCompanyId;
                return id;
            }
        });
    }

    // 2.开启异步任务,返回future
    public static CompletableFuture<String> doSomethingTwo(String companyId) {
        return CompletableFuture.supplyAsync(new Supplier<String>() {

            @Override
            public String get() {

                // 2.1 休眠3s,模拟计算
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                // 2.2 查询公司信息,转换为str,并返回
                String str = companyId + ":alibaba";
                return str;
            }
        });
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // I,等doSomethingOne执行完毕后,接着执行doSomethingTwo
        CompletableFuture result = doSomethingOne("123").thenCompose(id -> doSomethingTwo(id));
        System.out.println(result.get());
    }
}

上述main函数中首先调用方法doSomethingOne(“123”)开启了一个异步任务,并返回了对应的CompletableFuture对象,我们取名为future1,然后在future1的基础上调用了thenCompose方法,企图让future1执行完毕后,激活使用其结果作为doSomethingTwo(String companyId)方法的参数的任务。


2)基于thenCombine实现当两个并发运行的CompletableFuture任务都完成后,使用两者的结果作为参数再执行一个异步任务,这里只需要把上面例子中的:

CompletableFuture result = doSomethingOne("123").thenCompose(id -> doSomethingTwo(id));

修改为:

result = doSomethingOne("123").thenCombine(doSomethingTwo("456"), (one, two) -> {
            return one + " " + two;
        });

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

public static void allOf() throws InterruptedException, ExecutionException {
    // 1.创建future列表
    List<CompletableFuture<String>> futureList = new ArrayList<>();
    futureList.add(doSomethingOne("1"));
    futureList.add(doSomethingOne("2"));
    futureList.add(doSomethingOne("3"));
    futureList.add(doSomethingOne("4"));

    // 2.转换多个future为一个
    CompletableFuture<Void> result = CompletableFuture
            .allOf(futureList.toArray(new CompletableFuture[futureList.size()]));

    // 3.等待所有future都完成
    System.out.println(result.get());
}

如上代码1调用了四次doSomethingOne方法,分别返回一个CompletableFuture对象,然后收集这些CompletableFuture到futureList列表。

代码2调用allOf方法把多个CompletableFuture转换为一个result,代码3在result上调用get()方法会阻塞调用线程,直到futureList列表中所有任务执行完毕才返回。

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

public static void anyOf() throws InterruptedException, ExecutionException {
    // 1.创建future列表
    List<CompletableFuture<String>> futureList = new ArrayList<>();
    futureList.add(doSomethingOne("1"));
    futureList.add(doSomethingOne("2"));
    futureList.add(doSomethingTwo("3"));

    // 2.转换多个future为一个
    CompletableFuture<Object> result = CompletableFuture
            .anyOf(futureList.toArray(new CompletableFuture[futureList.size()]));

    // 3.等待某一个future完成
    System.out.println(result.get());

}

如上代码1调用了四次doSomethingOne方法,分别返回一个CompletableFuture对象,然后收集这些CompletableFuture到futureList列表。

代码2调用anyOf方法把多个CompletableFuture转换为一个result,代码3在result上调用get()方法会阻塞调用线程,直到futureList列表中有一个任务执行完毕才返回。


异常处理

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

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
    // 1.创建一个CompletableFuture对象
    CompletableFuture<String> future = new CompletableFuture<String>();

    // 2.开启线程计算任务结果,并设置
    new Thread(() -> {

        // 2.1休眠3s,模拟任务计算
        try {
            // 2.1.1抛出异常
            if (true) {
                throw new RuntimeException("excetion test");
            }
            // 2.1.2设置正常结果
            future.complete("ok");
        } catch (Exception e) {

        }
        // 2.2设置计算结果到future
        System.out.println("----" + Thread.currentThread().getName() + " set future result----");

    }, "thread-1").start();

    // 3.等待计算结果
    System.out.println(future.get());
}

由上述代码可知,在代码2.1.2设置正常结果前,代码2.1.1抛出了异常,这会导致代码3一直阻塞,所以我们不仅需要考虑正常设置结果的情况,还需要考虑异常的情况,其实CompletableFuture提供了completeExceptionally方法来处理异常情况,将上述代码修改为如下所示。

public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
    // 1.创建一个CompletableFuture对象
    CompletableFuture<String> future = new CompletableFuture<String>();

    // 2.开启线程计算任务结果,并设置
    new Thread(() -> {

        // 2.1休眠3s,模拟任务计算
        try {
            // 2.1.1 抛出异常
            if (true) {
                throw new RuntimeException("excetion test");
            }
            // 2.1.2设置正常结果
            future.complete("ok");
        } catch (Exception e) {
            // 2.1.3 设置异常结果
            future.completeExceptionally(e);
        }
        // 2.2设置计算结果到future
        System.out.println("----" + Thread.currentThread().getName() + " set future result----");

    }, "thread-1").start();

    // 3.等待计算结果
    System.out.println(future.get());
}

如上代码2.1.3表示当出现异常时把异常信息设置到future内部,这样代码3就会在抛出异常后终止。

其实我们还可以修改代码3为:

System.out.println(future.exceptionally(t -> "default").get());// 默认值

实现当出现异常时返回默认值。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/981898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[管理与领导-73]:IT基层管理者 - 辅助技能 - 4- 职业发展规划 - 如何持续提升自我

目录 一、能力三核 1.1 专业知识能力 1.2 通用管理能力 1.3 优秀品质能力 二、能力发展策略 三、学习的四种渠道 四、有效的刻意练习 一、能力三核 1.1 专业知识能力 专业知识能力是指在特定领域或行业中所掌握的专业知识和技能。它是在特定领域内取得成功并为组织或个…

MySQL——DQL union合并、limit限制与DDL建表和删表

一、Union 合并 union:是实现两个查询结果的合并。 例如&#xff1a;当我们查询员工名字为manager 和 salesman的员工名字和 工作&#xff1f; select e.ename,e.job from emp e where e.jobmanager or e.job salesman; select e.ename,e.job from emp e where e.job in(man…

pandas笔记:显示中间的省略号

比如我们有这样一个数据&#xff08;Geolife中的数据&#xff09; 如何把中间的省略号完整地输出呢&#xff1f; pd.set_option(display.max_rows, None) data

【图解RabbitMQ-4】Docker安装RabbitMQ详细图文过程

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;CSDN实力新星&#xff0c;后端开发两年经验&#xff0c;曾担任甲方技术代表&#xff0c;业余独自创办智源恩创网络科技工作室。会点点Java相关技术栈、帆软报表、低代码平台快速开…

「网页开发|前端开发|Vue」06 公共组件与嵌套路由:让每一个页面都平等地拥有导航栏

本文主要介绍在多个页面存在相同部分时&#xff0c;如何提取公共组件然后在多个页面中导入组件重复使用来减少重复代码。在这基础上介绍了通过嵌套路由的方式来避免页面较多或公共部分较多的情况下&#xff0c;避免不断手动导入公共组件的麻烦&#xff0c;并且加快页面跳转的速…

烟感报警器单片机方案开发,解决方案

烟感报警器也叫做烟雾报警器。烟感报警器适用于火灾发生时有大量烟雾&#xff0c;而正常情况下无烟的场所。例如写字楼、医院、学校、博物馆等场所。烟感报警器一般安装于所需要保护或探测区域的天花板上&#xff0c;因火灾中烟雾比空气轻&#xff0c;更容易向上飘散&#xff0…

快速教程:如何更新AirPods固件!

让我们弄清楚一件事——你通常不需要学习如何更新AirPods固件。当苹果发布更新时&#xff0c;无论你使用的是标准的第三代AirPods还是AirPods Pro 2&#xff0c;固件都会自动安装在你的AirPods上。但是&#xff0c;这是一个很大的问题&#xff0c;但事情并不总是按计划进行。有…

【进阶篇】Redis缓存击穿, 穿透, 雪崩, 污染详解

【进阶篇】Redis缓存穿击, 穿透, 雪崩, 污染详解 文章目录 【进阶篇】Redis缓存穿击, 穿透, 雪崩, 污染详解0. 前言大纲缓存穿击缓存穿透缓存雪崩缓存污染 1. 什么是缓存穿透&#xff1f;1.1. 发生原因1.2. 导致问题1.3. 解决方案 2. 什么是缓存击穿3.1. 发生原因3.1. 解决方案…

PHP自己的框架留言板功能实现

目录 1、实现留言板功能效果 2、创建留言板数据表 3、控制器提交和显示方法 4、提交留言板html 5、留言板列表html 1、实现留言板功能效果 2、创建留言板数据表 CREATE TABLE msg (id int(11) NOT NULL AUTO_INCREMENT,name varchar(30) DEFAULT NULL COMMENT 留言板姓…

FullGC 40 次/天优化为 10 天 1 次

问题 前一段时间,线上服务器的 FullGC 非常频繁,平均一天 40 多次,而且隔几天就有服务器自动重启了,这表明服务器的状态已经非常不正常了,得到这么好的机会,当然要主动请求进行调优了。 未调优前的服务器 GC 数据,FullGC 非常频繁。 首先服务器的配置非常一般(2 核 4…

公园气象站:用科技力量,感知气象变化

在城市的喧嚣中&#xff0c;公园成为人们休闲娱乐的宁静之地。而在这些公园中的公园气象站静静地矗立着&#xff0c;不仅为公园的日常运营提供着重要数据&#xff0c;还在为游客的安全保驾护航。 用科技力量&#xff0c;感知气象变化 科技的创新为气象监测提供了更为精准的手…

iOS 16.4更新指南:问题解答与新功能一览

我应该更新到iOS 16.4吗&#xff1f;这是许多iPhone用户在新更新可用时问自己的一个常见问题。最新的iOS版本提供了各种功能和改进&#xff0c;因此更新的诱惑力很大。 但是&#xff0c;在更新之前&#xff0c;你应该考虑几个因素&#xff0c;以确保安装过程顺利成功。这些因素…

Android 九宫格布局

效果图 实现思路&#xff1a; 1.使用GridView来实现九宫格布局&#xff0c;设置numColumns3。 2.图标使用的是Font Awesome矢量图标&#xff0c;详情可以参考Android 在APP中使用 Font Awesome 图标_.fa-headphones_清山博客的博客-CSDN博客 实现步骤&#xff1a; 1.布局文…

关于vscode的GitLens插件里的FILE HISTORY理解

最近在用vscode的GitLens插件开发项目遇到这个疑问&#xff0c;先看图&#xff1a; 每当我点击FILE HISTORY 一个commit时&#xff0c;正常来说显示器会自动将点击的提交版本和它上一个提交版本进行比较&#xff0c;如果单纯这么理解的话就错了&#xff0c;因为GitLens的File …

基于循环队列和信号量的生产和消费者模型

这一节为什么要基于信号量来实现同一个模型&#xff0c;原因&#xff1a; void push(const T& in){pthread_mutex_lock(&_lock);while(is_Full()){//这里说明阻塞队列是满的&#xff0c;需要让生产者等待pthread_cond_wait(&_pcond,&_lock);}//这里说明阻塞队列…

jupyter 格式化与快捷键

1、标题&#xff1a; # 一级标题 ## 二级标题 ### 三级标题 2、 加粗文本&#xff1a; **加粗文本** 3、斜体文本&#xff1a; _斜体_ 4、删除线 ~删除线~ 5、高亮文本 高亮文本 6、区块引用 > 我是引用文字 >> 我是第二层 >&g…

提高广播新闻自动语音识别模型的准确性

语音识别技术的存在让机器能够听懂人类的语言&#xff0c;让机器理解人类的语言。语音识别技术发展至今&#xff0c;已经应运而上了各种各样的语音智能助手&#xff0c;可能有一天我们身边的物体都能和我们说话&#xff0c;万物相连的时代也如期而至。 数据从何而来&#xff1…

FPN模型

【简介】 2017年&#xff0c;T.-Y.Lin等人在Faster RCNN的基础上进一步提出了特征金字塔网络FPN(Feature Pyramid Networks)技术。在FPN技术出现之前&#xff0c;大多数检测算法的检测头都位于网络的最顶层(最深层)&#xff0c;虽说最深层的特征具备更丰富的语义信息&#xff0…

Mybatis 动态SQL - 使用foreach标签查询数据、批量新增、批量修改、删除数据

前面我们介绍了使用Mybatis完成数据的增删改查&#xff0c;并且也了解了如何在Mybatis中使用JDK的日志系统打印日志&#xff1b;本篇我们介绍使用Mybatis的动态SQL完成查询数据、批量新增、批量修改、删除数据。 如果您对数据的增删改查操作和Mybatis集成JDK日志系统不太了解&…

基于FPGA的RGB图像转化为灰度图实现,通过MATLAB进行辅助验证

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 vivado2019.2 matlab2022a 3.部分核心程序 timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 202…