Java并发/多线程CompleteableFuture详解

news2024/9/17 8:30:34

目录

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

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

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

相关文章

C++ 类和对象 构造 / 析构函数

一 类的6个默认成员函数&#xff1a; 如果一个类中什么成员都没有&#xff0c;简称为空类。 例&#xff1a; #include <iostream> class Empty {// 空类&#xff0c;什么成员都没有 }; 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&a…

使用预加载库优化 PostgreSQL 函数#postgresql认证

在 POSTGRESQL 中执行函数和过程 为了理解 PostgreSQL 的工作原理&#xff0c;我们首先要看一个简单的函数调用。下一个清单显示了一些简单的PostGIS代码&#xff1a; PgSQL test# timing Timing is on. test# SELECT * FROM hans.points WHERE id 1;id │ …

南大通用数据库-Gbase-8a-学习-44-DDLEVENT恢复

目录 一、环境信息 二、前景提要 1、情况描述 2、3号节点gc_recover日志截图 3、3号节点express日志截图 4、ddlevent截图 5、报错赋权语句分别在1节点和4节点执行 6、gcadmin 三、解决方法 1、描述 2、清理系统user表DDLEVENT 3、拷贝系统user表数据 &#xff08;…

【游戏客户端】大话slg玩法架构(一)滚动基类

【游戏客户端】大话slg玩法架构&#xff08;一&#xff09;滚动基类 大家好&#xff0c;我是Lampard家杰~~ 今天我们兑现诺言&#xff0c;给大家分享SLG玩法的实现j架构&#xff0c;关于SLG玩法的介绍可以参考这篇上一篇文章&#xff1a;【游戏客户端】制作率土之滨Like玩法 PS…

React -- useState状态更新异步特性——导致获取值为旧值的问题

useState状态异步更新 问题导致的原因解决办法进一步分析后续遇到的新问题 问题 const [isSelecting, setIsSelecting] useState(false);useEffect(() > {const handleKeyDown (event) > {if (event.key Escape) {if(isSelectingRef){//.......setIsSelecting(!isSele…

项目机会:4万平:智能仓,AGV,穿梭车,AMR,WMS,提升机,机器人……

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 如下为近期国内智能仓储物流相关项目的公开信息线索&#xff0c;这些项目具体信息会发布到知识星球&#xff0c;请感兴趣的球友先人一步到知识星球【智能仓储物流技术研习社】自行下载…

LeetCode热题100刷题9:25. K 个一组翻转链表、101. 对称二叉树、543. 二叉树的直径、102. 二叉树的层序遍历

25. K 个一组翻转链表 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *next) : val(x), nex…

Java | Leetcode Java题解之第223题矩形面积

题目&#xff1a; 题解&#xff1a; class Solution {public int computeArea(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) {int area1 (ax2 - ax1) * (ay2 - ay1), area2 (bx2 - bx1) * (by2 - by1);int overlapWidth Math.min(ax2, bx2) -…

代发考生战报:南京考场华为售前HCSP H19-411考试通过

代发考生战报&#xff1a;南京考场华为售前HCSP H19-411考试通过&#xff0c;客服给的题库非常稳定&#xff0c;考试遇到2个新题&#xff0c;剩下全是题库里的原题&#xff0c;想考的放心考吧&#xff0c;考场服务挺好&#xff0c;管理员带着做签名和一些考试说明介绍清楚&…

C++相关概念和易错语法(16)(list)

1.list易错点 &#xff08;1&#xff09;慎用list的sort&#xff0c;list的排序比vector慢得多&#xff0c;尽管两者时间复杂度一样&#xff0c;甚至不如先把list转为vector&#xff0c;用vector排完序后再转为list &#xff08;2&#xff09;splice是剪切链表&#xff0c;将…

AGAST (角点检测)

AGAST检测原理 AGAST(Adaptive and Generic Accelerated Segment Test)算法是Elmar于2010年提出的特征检测算法,改进了FAST(Features from Accelerated Segment Test)特征检测方法,使其具有更快的速度和更好的鲁棒性。AGAST算法提供了比FAST算法更详细的特征标记方式和判断依…

读人工智能全传07智能体

1. 布鲁克斯革命 1.1. 随着科学认知的发展&#xff0c;有时候旧有科学体系会面临全盘崩溃的危机&#xff0c;新的科学体系诞生&#xff0c;取代传统的、既定的科学体系&#xff0c;这就意味着科学的范式将发生变化 1.2. 澳大利亚机器人学家罗德尼布鲁克斯(Rodney Brooks)&…

vue3+ el-tree 展开和折叠,默认展开第一项

默认第一项展开: 展开所有项&#xff1a; 折叠所有项&#xff1a; <template><el-treestyle"max-width: 600px":data"treeData"node-key"id":default-expanded-keys"defaultExpandedKey":props"defaultProps"…

Qt creator 控件转到槽 报错 The class containing “Ui:Dialog“ could not be found in

今天调试程序&#xff0c;发现主界面控件转到槽&#xff0c;报错如下图&#xff1a; 问题表现为&#xff1a;只有主窗口控件有这个错误&#xff0c;其他子窗口正常。 解决&#xff1a; 在网上搜这个报错信息&#xff0c;都没有一个很好的解决办法。 最后发现是我在子窗口中要…

004-基于Sklearn的机器学习入门:回归分析(下)

本节及后续章节将介绍机器学习中的几种经典回归算法&#xff0c;包括线性回归&#xff0c;多项式回归&#xff0c;以及正则项的岭回归等&#xff0c;所选方法都在Sklearn库中聚类模块有具体实现。本节为下篇&#xff0c;将介绍多项式回归和岭回归等。 2.3 多项式回归 在一般的…

Visual Studio Code 教程 VsCode安装Live Server以服务形式打开html

搜索Live Server 插件,然后安装 选一个html文件&#xff0c;右键点击 Open with live server,然后就自动弹出来了

怎样优化 PostgreSQL 中对日期时间范围的模糊查询?

文章目录 一、问题分析&#xff08;一&#xff09;索引未有效利用&#xff08;二&#xff09;日期时间格式不统一&#xff08;三&#xff09;复杂的查询条件 二、优化策略&#xff08;一&#xff09;使用合适的索引&#xff08;二&#xff09;规范日期时间格式&#xff08;三&a…

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

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

Html5前端基本知识整理与回顾上篇

今天我们结合之前上传的知识资源来回顾学习的Html5前端知识&#xff0c;与大家共勉&#xff0c;一起学习。 目录 介绍 了解 注释 标签结构 排版标签 标题标签 ​编辑 段落标签 ​编辑 换⾏标签 ​编辑 ⽔平分割线 ⽂本格式化标签 媒体标签 绝对路径 相对路径 …

Chromium编译指南2024 Linux篇-安装官方工具depot_tools(二)

1.引言 在上一节中&#xff0c;我们已经完成了 Git 的安装&#xff0c;并了解了其在 Chromium 编译过程中的重要性。接下来&#xff0c;我们将继续进行环境的配置&#xff0c;首先是安装和配置 Chromium 编译所需的重要工具——depot_tools。 depot_tools 是一组用于获取、管…