Java并发/多线程CompleteableFuture详解

目录

CompleteableFuture

创建

获得结果的方法

辅助方法

allOf和anyOf的区别

CompletableFuture 里大约有五十种方法,但是可以进行归类:

变换类 thenApply

消费类 thenAccept

执行操作类 thenRun

thenApply/thenAccept/thenRun

结合转化类 thenCombine

结合转化类 thenCompose

结合消费类 thenAcceptBoth

运行后执行类 runAfterBoth

取最快转换类 applyToEither

取最快消费类 acceptEither

取最快运行后执行类 runAfterEither

异常补偿类 exceptionally

运行后记录结果类 whenComplete

运行后处理结果类 handle


CompleteableFuture

Java 的 1.5 版本引入了 Future,可以把它简单的理解为运算结果的占位符,它提供了两个方法来获取运算结果。

get():调用该方法线程将会无限期等待运算结果。

get(long timeout, TimeUnit unit):调用该方法线程将仅在指定时间 timeout 内等待结果,如果等待超时就会抛出 TimeoutException 异常。

Future 可以使用 Runnable 或 Callable 实例来完成提交的任务,它存在如下几个问题:

  • 阻塞调用 get() 方法会一直阻塞,直到等待直到计算完成,它没有提供任何方法可以在完成时通知,同时也不具有附加回调函数的功能。
  • 链式调用和结果聚合处理在很多时候我们想链接多个 Future 来完成耗时较长的计算,此时需要合并结果并将结果发送到另一个任务中,该接口很难完成这种处理。
  • 异常处理 Future 没有提供任何异常处理的方式。

JDK1.8 才新加入的一个实现类 CompletableFuture,很好的解决了这些问题,CompletableFuture 实现了 Future<T>, CompletionStage<T>两个接口。实现了Future 接口,意味着可以像以前一样通过阻塞或者轮询的方式获得结果。

Future一次只能执行一个任务,拿到一次结果,CompletableFuture处理链式任务,组合任务

创建

除了直接 new 出一个 CompletableFuture 的实例,还可以通过工厂方法创建 CompletableFuture 的实例

工厂方法:

Asynsc 表示异步,而 supplyAsync 与 runAsync 不同在于,supplyAsync 异步返 回一个结果,runAsync 是 void。第二个函数第二个参数表示是用我们自己创建的线程池,否则采用默认的 ForkJoinPool.commonPool()作为它的线程池。

获得结果的方法

public T get()

public T get(long timeout, TimeUnit unit)

public T getNow(T valueIfAbsent)

public T join()

getNow 有点特殊,如果结果已经计算完则返回结果或者抛出异常,否则返回给定的 valueIfAbsent 值。

join 返回计算的结果或者抛出一个 unchecked 异常(CompletionException),它和 get 对抛出的异常的处理有些细微的区别。

辅助方法

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
    return andTree(cfs, 0, cfs.length - 1);
}
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
    return orTree(cfs, 0, cfs.length - 1);
}
allOf和anyOf的区别
// 类说明:allOf和anyOf的区别
public class AllofAnyOf {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Random rand = new Random();
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000 + rand.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future1完成");
            return 100;
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000 + rand.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future2完成");
            return "abc";
        });
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000 + rand.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future3完成");
            return "123abc";
        });
        /*allOf会等待全部异步方法(future1,future2,future3)执行完后,执行 thenRun() 中的操作
        只要有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null*/
        CompletableFuture.allOf(future1,future2,future3).thenRun(()->{
            System.out.println("All done!");
        });
        /*anyOf方法会等待其中任一任务完成后,返回一个最先完成任务的CompletableFuture对象,
        通过 get()方法获取完成任务的结果。如果该任务执行异常,则抛出异常*/
        CompletableFuture<Object> f = CompletableFuture.anyOf(future1,future2,future3);
        System.out.println("f>>" + f.get());

        SleepTools.second(5);//使主线程休眠 5 秒,以确保异步任务有足够的时间执行
    }
}

运行结果:

allOf 方法是当所有的 CompletableFuture 都执行完后执行计算。

anyOf 方法是当任意一个 CompletableFuture 执行完后就会执行计算,计算的结果相同。

CompletionStage 是一个接口,从命名上看得知是一个完成的阶段,它代表了一个特定的计算的阶段,可以同步或者异步的被完成。你可以把它看成一个计算流水线上的一个单元,并最终会产生一个最终结果,这意味着几个CompletionStage 可以串联起来,一个完成的阶段可以触发下一阶段的执行,接着触发下一次,再接着触发下次,….。

总结 CompletableFuture 几个关键点:

1、计算可以由 Future ,Consumer 或者 Runnable 接口中的 apply,accept 或者 run 等方法表示。

2、计算的执行主要有以下

  •   默认执行
  • 使用默认的 CompletionStage 的异步执行提供者异步执行。这些方法名使用 someActionAsync 这种格式表示。
  • 使用 Executor 提供者异步执行。这些方法同样也是 someActionAsync 这种格式,但是会增加一个 Executor 参数。

CompletableFuture 里大约有五十种方法,但是可以进行归类:

变换类 thenApply

public class ThenApplyExample {
    public static void main(String[] args) {
        //thenApply() 方法是一个同步方法,它在当前线程执行,
        //它接受一个 Function 参数,用于对前一个阶段(即前一个 CompletableFuture 对象)的结果进行转换。
        //通过 join() 方法等待异步任务完成
        String result = CompletableFuture.supplyAsync(() -> "hello").thenApply(s -> s + " world").join();
        System.out.println(result);//hello world

        //thenApplyAsync() 方法是一个异步方法,它会在一个新的线程中执行。
        //它也接受一个 Function 参数,用于对前一个阶段(前一个 CompletableFuture 对象)的结果进行转换。
        //通过 join() 方法等待异步任务完成
        String result2 = CompletableFuture.supplyAsync(() -> "hello2").thenApplyAsync(s -> s + " world2").join();
        System.out.println(result2);//hello2 world2
    }
}

关键入参是函数式接口 Function。它的入参是上一个阶段计算后的结果,返回值是经过转化后结果。

thenApply() 方法是一个同步方法,它在当前线程执行。

thenApplyAsync() 方法是一个异步方法,它会在一个新的线程中执行。是异步执行的,它不会阻塞当前线程,允许多个任务并行执行,提高了任务处理的效率。

public class ThenApplyExample2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个ForkJoinPool线程池
        ForkJoinPool pool= new ForkJoinPool();
        // 使用CompletableFuture的supplyAsync方法,提交一个异步任务到ForkJoinPool线程池中执行
        CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(()->{
            // 打印当前线程的信息和任务开始标志
            System.out.println(Thread.currentThread()+" cf start");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace(); // 捕获并打印中断异常
            }
            System.out.println(Thread.currentThread()+" cf end");
            return 100;// 返回计算结果
        },pool);
        // thenApply方法会在cf异步任务完成后执行,cf任务的返回结果将作为thenApply方法的输入参数
        // thenApply会创建一个新的CompletableFuture实例
        CompletableFuture<String> cf2 =cf.thenApply((result)->{
            System.out.println(Thread.currentThread()+" cf2 start");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()+" cf2 end");
            return "test:"+result;
        });
        // 调用cf的get方法,等待cf任务完成并获取其返回结果
        System.out.println("run result->"+cf.get());
        System.out.println("run result->"+cf2.get());
    }
}

运行结果:

将ThenApplyExample2的thenApply方法改成thenApplyAsync后,运行结果如下:

thenApplyAsync默认使用ForkJoinPool.commonPool(),也可以指定执行异步任务的Executor实现。 下面列举的每个方法都有两个以Async结尾的方法,一个使用默认的Executor实现,一个使用指定的Executor实现,不带Async的方法是由触发该任务的线程执行该任务,带Async的方法是由触发该任务的线程将任务提交到线程池,执行任务的线程跟触发任务的线程不一定是同一个 。

消费类 thenAccept

public class ThenAcceptAsyncExample {
    public static void main(String[] args) {
        // 创建一个异步任务,返回字符串 "hello"
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "hello");

        // 使用 thenAccept 方法消费前一个阶段的结果,并输出结果
        future1.thenAccept(s -> System.out.println(s + " world")); // 输出:hello world

        // 创建一个异步任务,返回字符串 "hello2"
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "hello2");

        // 使用 thenAcceptAsync 方法异步消费前一个阶段的结果,并输出结果
        future2.thenAcceptAsync(s -> System.out.println(s + " world2")); // 输出:hello2 world2
    }
}

关键入参是函数式接口 Consumer。它的入参是上一个阶段计算后的结果, 没有返回值。

thenAccept() 方法是一个同步方法,它在当前线程执行。它接受一个 Consumer 参数,用于消费前一个阶段(前一个 CompletableFuture 对象)的结果。

thenAcceptAsync() 方法是一个异步方法,它会在一个新的线程中执行。它接受一个 Consumer 参数,用于消费前一个阶段(前一个 CompletableFuture 对象)的结果。这样的异步处理允许多个任务并行执行,提高了任务处理的效率。

执行操作类 thenRun

public class ThenRunAsyncExample {
    public static void main(String[] args) {
        // 创建一个异步任务,休眠 1 秒后返回字符串 "hello"
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "hello";
        }).thenRun(() -> System.out.println("hello world")); // 输出:hello world

        // 创建一个异步任务,休眠 1 秒后返回字符串 "hello2"
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "hello2";
        }).thenRunAsync(() -> System.out.println("hello world2")); // 输出:hello world2

        // 等待所有异步任务完成
        SleepTools.second(2);
    }
}

对上一步的计算结果不关心,执行下一个操作,入参是一个 Runnable 的实例,表示上一步完成后执行的操作。

thenRun() 方法和 thenRunAsync() 方法都用于执行一些操作,但不返回任何结果。它们主要用于链式调用时,处理一些与结果无关的逻辑。

thenRun() 方法是同步方法,在当前线程执行。thenRunAsync() 方法是异步方法,会在新的线程中执行,允许多个任务并行执行。

thenApply/thenAccept/thenRun

public class ThenExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个ForkJoinPool线程池
        ForkJoinPool pool=new ForkJoinPool();
        // 使用CompletableFuture的supplyAsync方法,提交一个异步任务到ForkJoinPool线程池中执行
        CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread()+" cf start ");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()+" cf end ");
            return 100;
        },pool);

        // thenApply方法:cf任务完成后执行,cf任务的返回结果作为thenApply方法的输入参数
        CompletableFuture cf2 = cf.thenApply((result)->{
            System.out.println(Thread.currentThread()+" cf2 thenApply start ");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()+" cf2 thenApply end");
            return "test:"+result;
        }).thenAccept((result)-> { // thenAccept方法:cf2任务完成后执行,接收cf2的结果作为输入参数,但没有返回值
            System.out.println(Thread.currentThread()+" cf3 thenAccept start ");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("cf2-2 "+ result);
            System.out.println(Thread.currentThread()+" cf3 thenAccept end");
        }).thenRun(()->{ // thenRun方法:cf3任务完成后执行,无输入参数,也没有返回值
            System.out.println(Thread.currentThread()+" cf4 thenRun start ");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thenRun...");
            System.out.println(Thread.currentThread()+" cf4 thenRun end");
        });
        //等待子任务执行完成
        System.out.println("cf->"+cf.get());
        //cf2 等待最后一个thenRun执行完成
        System.out.println("cf2->"+cf2.get());
    }
}

运行结果:

结合转化类 thenCombine

public class ThenCombineAsyncExample {
    public static void main(String[] args) {
        // 创建两个异步任务
        //thenCombine() 方法是一个同步方法,它在当前线程执行。
        //它接受两个参数:第一个参数是另一个 CompletableFuture 对象,第二个参数是一个 BiFunction
        //用于对两个 CompletableFuture 对象的结果进行组合。并通过 BiFunction 将它们拼接成 "hello world"。
        String result = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "hello";
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "world";
        }), (s1, s2) -> s1 + " " + s2).join();
        System.out.println(result); // 输出:hello world

        // 创建两个异步任务
        String result2 = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "hello2";
        }).thenCombineAsync(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "world2";
        }), (s1, s2) -> s1 + " " + s2).join();
        System.out.println(result2); // 输出:hello2 world2
    }
}

需要上一步的处理返回值,并且 other 代表的 CompletionStage 有返回值之后,利用这两个返回值,进行转换后返回指定类型的值。

两个 CompletionStage 是并行执行的,它们之间并没有先后依赖顺序,other 并不会等待先前的 CompletableFuture 执行完毕后再执行。

它接受两个参数:第一个参数是另一个 CompletableFuture 对象,第二个参数是一个 BiFunction 用于对两个 CompletableFuture 对象的结果进行组合。

thenCombine() 方法是同步方法,在当前线程执行。thenCombineAsync() 方法是异步方法,会在新的线程中执行,允许多个任务并行执行。

thenCombine() 方法和 thenCombineAsync() 方法返回一个新的 CompletableFuture 对象,表示两个异步任务的组合结果。您可以通过 get() 方法或 join() 方法等待组合后的结果,并对其进行后续操作。

thenCombine() 和 thenCombineAsync() 方法允许您在两个异步任务的结果上执行进一步的操作,并组合它们的结果,以实现更复杂的异步任务处理逻辑。

//也可以这样写
public class ThenCombineExample2 {
    public static void main(String[] args) {
        CompletableFuture<String> future3  = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            System.out.println("future3");
            return "hello";
        });
        CompletableFuture<String> future4 = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            System.out.println("future4");
            return "word";
        });
        CompletableFuture<String> future5 = future3.thenCombine(future4, (s1, s2)->s1 + s2);
        System.out.println("result: " + future5.join());
    }
}
//运行结果
future3
future4 
result: helloword

结合转化类 thenCompose

public class ThenComposeAsyncExample {
    public static void main(String[] args) {
        // 创建一个异步任务
        //thenCompose() 方法是一个同步方法,它在当前线程执行。
        //它接受一个 Function 参数,该函数对前一个阶段(即前一个 CompletableFuture 对象)的结果进行处理,并返回一个新的 CompletableFuture 对象。
        //可以通过 get() 方法或 join() 方法等待组合后的结果,并对其进行后续操作。
        Integer result = CompletableFuture.supplyAsync(() -> {
            return 10;
        }).thenCompose(i -> CompletableFuture.supplyAsync(() -> { return i + 1; })).join();
        System.out.println(result); // 输出:11

        // 创建一个异步任务
        // thenComposeAsync() 方法是一个异步方法,它会在一个新的线程中执行。
        Integer result2 = CompletableFuture.supplyAsync(() -> {
            return 100;
        }).thenComposeAsync(i -> CompletableFuture.supplyAsync(() -> { return i + 2; })).join();
        System.out.println(result2); // 输出:102
    }
}

对于 Compose 可以连接两个 CompletableFuture,其内部处理逻辑是当第一个 CompletableFuture 处理没有完成时会合并成一个 CompletableFuture,如果处理完成,第二个 future 会紧接上一个 CompletableFuture 进行处理。 第一个 CompletableFuture 的处理结果是第二个 future 需要的输入参数。

结合消费类 thenAcceptBoth

public class ThenAcceptBothAsyncExample {
    public static void main(String[] args) {
        // 创建两个异步任务
        //thenAcceptBoth() 方法是一个同步方法,它在当前线程执行。
        //它接受两个参数:第一个参数是另一个 CompletableFuture 对象,第二个参数是一个 BiConsumer,用于消费这两个 CompletableFuture 对象的结果。
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "hello";
        }).thenAcceptBoth(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "world";
        }), (s1, s2) -> System.out.println(s1 + " " + s2)); // 输出:hello world

        // 等待一段时间,确保组合任务有足够时间完成
        SleepTools.second(3); // 注意:这里需要足够长的等待时间,以确保组合任务完成输出结果

        // 创建两个异步任务
        // thenAcceptBothAsync() 方法是一个异步方法,它会在一个新的线程中执行。
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "hello2";
        }).thenAcceptBothAsync(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "world2";
        }), (s1, s2) -> System.out.println(s1 + " " + s2)); // 输出:102

        // 等待一段时间,确保组合任务有足够时间完成
        SleepTools.second(3);
    }
}

需要上一步的处理返回值,并且 other 代表的 CompletionStage 有返回值之后,利用这两个返回值,进行消费,允许您在两个异步任务的结果上执行消费操作,实现多个任务的组合处理。

运行后执行类 runAfterBoth

public class RunAfterBothAsyncExample {
    public static void main(String[] args) {
        // 创建两个异步任务
        // runAfterBoth() 方法是一个同步方法,它在当前线程执行。
        // 它接受两个参数:第一个参数是另一个 CompletableFuture 对象,
        // 第二个参数是一个 Runnable,用于指定在两个 CompletableFuture 对象都完成后需要执行的任务。
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "s1";
        }).runAfterBoth(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "s2";
        }), () -> System.out.println("hello world1")); // 输出:hello world1

        // 创建两个异步任务,休眠 1 秒和 2 秒后分别返回字符串 "s3" 和 "s4"
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "s3";
        }).runAfterBothAsync(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "s4";
        }), () -> System.out.println("hello world2")); // 输出:hello world2

        // 等待一段时间,确保异步任务有足够时间完成
        SleepTools.second(3); // 注意:这里需要足够长的等待时间,以确保异步任务完成输出结果
    }
}

不关心这两个 CompletionStage 的结果,只关心这两个 CompletionStage 都执行完毕,之后再进行操作(Runnable)。

runAfterBoth() 方法和 runAfterBothAsync() 方法用于在两个异步任务都完成后执行一个 Runnable 任务,适用于需要在两个异步任务都完成后进行后续操作的场景。

取最快转换类 applyToEither

public class ApplyToEitherAsyncExample {
    public static void main(String[] args) {
        // 创建两个异步任务
        // applyToEither() 方法是一个同步方法,它在当前线程执行。
        // 它接受两个参数:第一个参数是另一个 CompletableFuture 对象,
        // 第二个参数是一个 Function,用于对第一个完成的 CompletableFuture 对象的结果进行处理。
        // 在两个任务中的任意一个完成后,将其结果应用到一个函数,并返回最先完成任务的结果。
        String result = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "S1";
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "S2";
        }), s -> s).join();
        System.out.println(result); // 输出:S1 或 S2,取决于哪个任务先完成

        // 创建两个异步任务
        String result2 = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "S3";
        }).applyToEitherAsync(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "S4";
        }), s -> s).join();
        System.out.println(result2); // 输出:S3 或 S4,取决于哪个任务先完成
    }
}

两个 CompletionStage,谁计算的快,我就用那个 CompletionStage 的结果进行下一步的转化操作。现实开发场景中,总会碰到有两种渠道完成同一个事情, 所以就可以调用这个方法,找一个最快的结果进行处理。

取最快消费类 acceptEither

public class AcceptEitherAsyncExample {
    public static void main(String[] args) {
        // 创建两个异步任务
        // acceptEither() 方法是一个同步方法,它在当前线程执行。
        // 它接受两个参数:第一个参数是另一个 CompletableFuture 对象,
        // 第二个参数是一个 Consumer,用于对第一个完成的 CompletableFuture 对象的结果进行消费操作。
        // 在两个任务中的任意一个完成后,执行一个消费操作将其结果输出。
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "S1";
        }).acceptEither(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "S2";
        }), (s) -> System.out.println(s)); // 输出:S1 或 S2,取决于哪个任务先完成

        // 创建两个异步任务
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "S3";
        }).acceptEitherAsync(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "S4";
        }), (s) -> System.out.println(s)); // 输出:S3 或 S4,取决于哪个任务先完成

        // 等待一段时间,确保异步任务有足够时间完成
        SleepTools.second(3); // 注意:这里需要足够长的等待时间,以确保异步任务完成输出结果
    }
}

两个 CompletionStage,谁计算的快,我就用那个 CompletionStage 的结果进行下一步的消费操作。

acceptEither() 和 acceptEitherAsync() 方法允许您在两个异步任务中的任意一个完成后,执行一个消费操作,适用于需要对最先完成任务结果进行处理的场景。

取最快运行后执行类 runAfterEither

public class RunAfterEitherExample {
    public static void main(String[] args) {
        // 创建两个异步任务
        // runAfterEither() 方法是一个同步方法,它在当前线程执行。
        // 它接受两个参数:第一个参数是另一个 CompletableFuture 对象,
        // 第二个参数是一个 Runnable,用于指定在任意一个 CompletableFuture 对象完成后需要执行的任务。
        // 指定在这两个任务中的任意一个完成后输出 "hello world"。
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "S1";
        }).runAfterEither(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "S2";
        }), () -> System.out.println("hello world")); // 输出:"hello world",任何一个任务完成都会输出

        // 创建两个异步任务
        CompletableFuture.supplyAsync(() -> {
            SleepTools.second(2);
            return "S3";
        }).runAfterEither(CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "S4";
        }), () -> System.out.println("hello world2")); // 输出:"hello world2",任何一个任务完成都会输出

        // 等待一段时间,确保异步任务有足够时间完成
        SleepTools.second(3); // 注意:这里需要足够长的等待时间,以确保异步任务完成输出结果
    }
}

两个 CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)。 适用于需要在任意一个任务完成后进行后续操作的场景。

异常补偿类 exceptionally

public class ExceptionallyExample {
    public static void main(String[] args) {
        // 创建一个异步任务
        // exceptionally() 方法是一个同步方法,它在当前线程执行。
        // 它接受一个 Function 参数,用于处理异步任务发生的异常。
        // 如果异步任务发生异常,exceptionally() 方法会返回一个新的 CompletableFuture 对象,该对象会返回由处理函数提供的默认值。
        // 因为任务发生了异常,exceptionally() 方法会捕获该异常,并返回一个新的 CompletableFuture 对象,该对象的结果由处理函数 "hello world" 提供。
        String result = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            if (1 == 1) {
                throw new RuntimeException("测试一下异常情况");
            }
            return "S1";
        }).exceptionally(e -> {
            System.out.println(e.getMessage()); // 输出:"测试一下异常情况"
            return "hello world";
        }).join();
    	// 输出:"hello world",因为发生异常,所以返回默认值 "hello world",未发生异常输出"S1"
        System.out.println(result); 
    }
}

当运行时出现了异常,可以通过 exceptionally 进行补偿。

在使用 exceptionally() 方法时,如果异步任务没有发生异常,则会返回原始结果;如果发生了异常,则会返回处理函数提供的默认值。

exceptionally() 方法允许您在异步任务发生异常时进行恢复操作,返回一个默认值或其他处理结果,适用于需要对异步任务的异常进行处理的场景。

运行后记录结果类 whenComplete

public class WhenCompleteAndExceptionallyExample {
    public static void main(String[] args) {
        // 创建一个异步任务
        // whenComplete()方法是一个同步方法,它在当前线程执行。它接受一个 BiConsumer 参数,用于在异步任务完成或发生异常时进行处理。
        // BiConsumer 接收两个参数:第一个参数是异步任务的结果(如果成功完成),第二个参数是异步任务抛出的异常(如果发生异常)。
        // whenComplete() 方法会捕获该异常,并执行处理函数打印异常信息。

        // exceptionally() 方法是一个同步方法,它在当前线程执行。它接受一个 Function 参数,用于处理异步任务发生的异常。
        // 如果异步任务发生异常,exceptionally() 方法会捕获该异常,并返回一个新的 CompletableFuture 对象,该对象会返回由处理函数提供的默认值。
        // 通过 exceptionally() 方法在任务发生异常后,捕获该异常并返回一个新的 CompletableFuture 对象,该对象的结果由处理函数 "hello world" 提供。
        String result = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            if (1 == 1) {
                throw new RuntimeException("测试一下异常情况");
            }
            return "s1";
        }).whenComplete((s, t) -> {
            System.out.println(s); // 输出null,因为发生了异常
            System.out.println(t.getMessage());
        }).exceptionally(e -> {
            System.out.println(e.getMessage());
            return "hello world";
        }).join();

        System.out.println(result); // 输出:"hello world",因为发生异常,所以返回默认值 "hello world"
    }
}

//运行结果
null
java.lang.RuntimeException: 测试一下异常情况
java.lang.RuntimeException: 测试一下异常情况
hello world

//注释if (1 == 1) {throw new RuntimeException("测试一下异常情况");}
s1
java.lang.NullPointerException
hello world

执行完毕后它的结果返回原始的 CompletableFuture 的计算结果或者返回异常。所以不会对结果产生任何的作用。

whenComplete() 方法用于在异步任务完成或发生异常时进行处理,不影响最终的返回结果,它可以用来记录日志、清理资源等,不改变原始计算的结果或异常。

exceptionally() 方法用于在异步任务发生异常时进行处理,返回一个默认值或其他处理结果,用于恢复异常情况。

通过结合使用 whenComplete() 和 exceptionally() 方法,可以在处理异步任务时对任务的结果和异常进行灵活处理。适用于需要对异步任务的结果或异常进行特定操作的场景,例如,可以在 whenComplete 中记录日志、清理资源,并在 exceptionally 中返回默认值以恢复任务的结果。

运行后处理结果类 handle

public class HandleExample {
    public static void main(String[] args) {
        // 创建一个异步任务,休眠 1 秒后抛出异常
        // handle() 方法是一个同步方法,它在当前线程执行。它接受一个 BiFunction 参数,用于处理异步任务的结果或异常。
        // BiFunction 接收两个参数:第一个参数是异步任务的结果(如果成功完成),第二个参数是异步任务抛出的异常(如果发生异常)。
        // 发生了异常 handle() 方法会捕获该异常,并返回一个新的结果 "hello world",用于处理异常情况。
        String result = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            // 出现异常
            if (1 == 1) {
                throw new RuntimeException("测试一下异常情况");
            }
            return "s1";
        }).handle((s, t) -> { // 出现异常 s=null, t=java.util.concurrent.CompletionException: java.lang.RuntimeException: 测试一下异常情况
            if (t != null) {
                return "hello world"; // 处理异常情况,返回 "hello world"
            }
            return s; // 返回原始结果 "s1",因为没有异常发生
        }).join();

        System.out.println(result); // 输出:"hello world",因为发生异常,返回处理后的结果 "hello world"

        // 创建一个异步任务,休眠 1 秒后返回结果 "s1"
        String result2 = CompletableFuture.supplyAsync(() -> {
            SleepTools.second(1);
            return "s1";
        }).handle((s, t) -> { // 未出现异常 s="s1" , t=null
            if (t != null) {
                return "hello world"; // 处理异常情况,返回 "hello world"
            }
            return s; // 返回原始结果 "s1",因为没有异常发生
        }).join();

        System.out.println(result2); // 输出:"s1",因为没有发生异常,返回原始结果 "s1"
    }
}

运行完成时,对结果的处理。这里的完成时有两种情况,一种是正常执行, 返回值。另外一种是遇到异常抛出造成程序的中断 。

handle() 方法用于在异步任务完成或发生异常时进行处理,可以根据是否发生异常来返回不同的结果。适用于需要对异步任务的结果或异常进行特定处理的场景。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

浅析Nginx技术:开源高性能Web服务器与反向代理

什么是Nginx&#xff1f; Nginx是一款轻量级、高性能的HTTP和反向代理服务器&#xff0c;也可以用作邮件代理服务器。它最初由俄罗斯的程序员Igor Sysoev在2004年开发&#xff0c;并于2004年首次公开发布。Nginx的主要优势在于其非阻塞的事件驱动架构&#xff0c;能够处理大量并…

python-24-零基础自学python while循环+交互+数据的存储

学习内容&#xff1a;《python编程&#xff1a;从入门到实践》第二版 知识点&#xff1a; 文件处理 with open&#xff08;&#xff09;while 练习内容&#xff1a;10章练习题10-3、10-4、10-5 练习10-3&#xff1a;访客 编写一个程序&#xff0c;提示用户输入名字。用户做…

北森锐途人才竞聘盘点管理测评:高管领导力六大评判标准深度解析万达商管中国绿发等

北森锐途人才管理测评&#xff1a;高管领导力评判标准深度解析 在企业高管的盘点与竞聘测评领域&#xff0c;众多管理人才面临评估自身领导力的挑战。面对能力卓越、职级显赫的同僚&#xff0c;许多管理者感到缺乏一套权威且专业的评价体系。然而&#xff0c;无论是天赋异禀的领…

【回溯 - 1】46. 全排列

46. 全排列 难度&#xff1a;中等 力扣地址&#xff1a;https://leetcode.cn/problems/permutations/description/ 问题描述 给定一个 不含重复数字 的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nu…

ICMP隧道

后台私信找我获取工具 目录 ICMP隧道作用 ICMP隧道转发TCP上线MSF 开启服务端 生成后门木马 msf开启监听 开启客户端icmp隧道 执行后门木马&#xff0c;本地上线 ICMP隧道转发SOCKS上线MSF 开启服务端 生成后门木马 msf开启监听 开启客户端icmp隧道 ​执行后…

经常用借呗和花呗对征信有影响吗?

说起支付宝里的花呗和借呗&#xff0c;大伙儿肯定都不陌生&#xff0c;它们俩就像是支付宝里的信用贷款双胞胎&#xff0c;名字相近&#xff0c;性格却大相径庭。现在&#xff0c;这俩兄弟都乖乖地接入了央行的征信大家庭&#xff0c;你的每一次使用&#xff0c;都会被记录得清…

老师怎么快速发布成绩?

期末考试的钟声刚刚敲响&#xff0c;成绩单的发放却成了老师们的一大难题。每当期末成绩揭晓&#xff0c;老师们便要开始一项繁琐的任务——将每一份成绩单逐一私信给家长。这不仅耗费了大量的时间和精力&#xff0c;也让本就忙碌的期末工作变得更加繁重。然而&#xff0c;随着…

生产力工具|Endnote X9如何自动更新文件信息

一、以EndNote X9.2版本为例&#xff0c;打开EndNote文献管理软件。 二、在菜单栏找到“Edit→Preferences...”&#xff0c;点击打开&#xff0c;弹出一个“EndNote Preferences”窗口。 三、进行设置 在打开的窗口左侧选择“PDF Handing”&#xff0c;右边会出现自动导入文献…

SwiftUI知识点(二)

Animation import SwiftUIstruct AnimationBootcamp: View {State var isAnimation: Bool falsevar body: some View {VStack{Button("Button"){withAnimation(Animation.default//重复//autoreverses: true&#xff1a;A-B-A-B//false: A-B&#xff0c;A-B.repeat…

[图解]SysML和EA建模住宅安全系统-13-时间图

1 00:00:00,480 --> 00:00:02,280 首先&#xff0c;我们来看&#xff0c;图画在哪里 2 00:00:02,290 --> 00:00:04,380 这个图 3 00:00:04,390 --> 00:00:06,180 你看&#xff0c;它是描述&#xff0c;刚才讲的 4 00:00:06,190 --> 00:00:09,010 描述这个活动 …

STM32学习历程(day5)

EXTI外部中断 中断 中断就是在主程序运行过程中 出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;CPU会暂停当前的程序&#xff0c;去处理中断程序 处理完会返回被暂停的位置 继续运行原来的程序。 中断优先级 当有多个中断源同时申请中断时 CPU会根据…

设计模式之职责链模式(Chain of Responsibility Pattern)

1.概念 职责链模式&#xff08;Chain of Responsibility Pattern&#xff09;&#xff1a;避免将请求发送者与接收者耦合在一起&#xff0c;让多个对象都有机会接收请求&#xff0c;将这些对象连接成一条链&#xff0c;并且沿着这条链传递请求&#xff0c;直到有对象处理它为止…

单例模式(大话设计模式)C/C++版本

单例模式 C 饿汉 /* HM hungry man 饿汉 */ #include <iostream> using namespace std; class Singleton { private:Singleton() { cout << "单例对象创建&#xff01;" << endl; };Singleton(const Singleton &);Singleton &operator(c…

【ARMv8/v9 GIC 系列 2.4 -- GIC SGI 和 PPI 中断的启用配置】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 GIC SGI 和 PPI 中断的使能配置GICR_ISENABLER0 操作使用举例SummaryGIC SGI 和 PPI 中断的使能配置 GICR_ISENABLER0寄存器(中断设置-使能寄存器0)用于启用相应的SGI(软件生成中断)或PPI(专用外设中断)向CPU接口的转发。每个…

Android多开应用软件系统设计

设计一个支持Android多开应用的软件系统&#xff0c;主要涉及到以下几个关键技术点和设计考虑&#xff1a; 1. 虚拟化技术 容器技术&#xff1a;与传统的虚拟机不同&#xff0c;可以采用更轻量级的容器技术&#xff0c;为每个应用实例创建独立的运行环境。这包括分配独立的用…

atcoder 357 F Two Sequence Queries (线段树板子)

题目&#xff1a; 分析&#xff1a; 线段树 代码&#xff1a; // Problem: F - Two Sequence Queries // Contest: AtCoder - SuntoryProgrammingContest2024&#xff08;AtCoder Beginner Contest 357&#xff09; // URL: https://atcoder.jp/contests/abc357/tasks/abc357_…

AI实时免费在线图片工具6:以图生相似图

1、以图生图&#xff0c;生成相似图 https://huggingface.co/spaces/diffusers/unofficial-SDXL-Turbo-i2i-t2i 间接实现&#xff1a;可以是图片先提取描述&#xff0c;再通过描述再去生成新图片 https://huggingface.co/spaces/gokaygokay/KolorsPlusPlus

JAVA基础-----128陷阱

一、何为128陷阱 Java中Integer类型在使用比较时的特殊行为------128陷阱&#xff0c;解释了当数值在-128到127范围内&#xff0c;由于valueOf方法的缓存机制导致地址相同&#xff0c;比较为真&#xff1b;超出这个范围则新分配内存&#xff0c;地址不同&#xff0c;比较为假。…

动态数据库设计

动态数据库设计是一种灵活的方法&#xff0c;用于构建能够适应不断变化的数据需求的数据库结构。它强调在不频繁修改数据库表结构的前提下&#xff0c;有效管理和存储多样化的数据。以下是实现动态数据库设计的一些关键技术点和策略&#xff1a; 实体-属性-值&#xff08;EAV&a…

安卓项目中so库选择

接上篇Android中常见SDK类型区别-CSDN博客 一些重要的加密算法或者核心协议一般都在C中编写&#xff0c;然后给java调用。这样可以避免反编译后查看到应用的源码。此时就需要了解一下NDK中的ABI&#xff08;Application Binary Interface的缩写&#xff0c;也就是应用二进制接…