介绍之前,先看一个入门使用案例,案例为执行异步任务(无返回值)

CompletableFuture.runAsync(() -> {
            System.out.println("异步执行任务");
        });
        System.out.println("主线程继续执行");

结果:

主线程继续执行
异步执行任务

我们发现,其实使用CompletableFuture执行异步任务。

一、介绍

简单的任务,用Future获取结果还好,但我们并行提交的多个异步任务,往往并不是独立的,很多时候业务逻辑处理存在串行[依赖]、并行、聚合的关系。如果要我们手动用 Fueture 实现,是非常麻烦的。

CompletableFuture是Future接口的扩展和增强。CompletableFuture实现了Future接口,并在此基础上进行了丰富地扩展,完美地弥补了Future上述的种种问题。

更为重要的是,CompletableFuture实现了对任务的编排能力。借助这项能力,我们可以轻松地组织不同任务的运行顺序、规则以及方式。从某种程度上说,这项能力是它的核心能力。而在以往,虽然通过CountDownLatch等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力且难以维护。

 CompletionStage接口: 执行某一个阶段,可向下执行后续阶段。异步执行,默认线程池是ForkJoinPool.commonPool()

二、应用场景

1、描述依赖关系

(1)、thenApply() 把前面异步任务的结果,交给后面的Function

例子:使用thenApply方法可以在计算完成后对结果进行转换

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
                .thenApply(s -> s + " World")
                .thenApply(String::toUpperCase);
        try {
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

结果:HELLO WORLD

(2)、thenCompose()用来连接两个有依赖关系的任务,结果由第二个任务返回

thenCompose方法用于将一个CompletableFuture的结果作为另一个CompletableFuture的输入,形成链式调用。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
                .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));
        try {
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

结果:Hello World

2、描述and聚合关系

(1)、thenCombine 任务合并,有返回值

thenCombine方法用于组合两个独立的CompletableFuture的结果

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " World");
        CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + s2);
        try {
            System.out.println(combined.get());
        }catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

结果:Hello World

(2)、thenAcceptBoth 两个任务执行完成后,将结果交给thenAccepetBoth消耗,无返回值

        // 模拟异步获取体重
        CompletableFuture<Double> weightFuture = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            double weight = 70.0;
            System.out.println(Thread.currentThread().getName() + " --- 体重获取完成: " + weight);
            return weight;
        });

        // 模拟异步获取身高
        CompletableFuture<Double> heightFuture = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            double height = 1.72;
            System.out.println(Thread.currentThread().getName() + " --- 身高获取完成: " + height);
            return height;
        });

        // 两个任务都完成后,计算并输出BMI状态
        weightFuture.thenAcceptBoth(heightFuture, (weight, height) -> {
            double bmi = weight / (height * height);
            System.out.println(Thread.currentThread().getName() + " --- BMI计算完成: " + String.format("%.3f", bmi));
            if (bmi < 18.5) {
                System.out.println("状态: 偏瘦");
            } else if (bmi <= 23.9) {
                System.out.println("状态: 正常");
            } else if (bmi <= 27.9) {
                System.out.println("状态: 偏胖");
            } else {
                System.out.println("状态: 肥胖");
            }
        }).join(); // 等待整个链式任务完成

结果:

ForkJoinPool.commonPool-worker-2 --- 身高获取完成: 1.72
ForkJoinPool.commonPool-worker-1 --- 体重获取完成: 70.0
ForkJoinPool.commonPool-worker-1 --- BMI计算完成: 23.661
状态: 正常

(3)、runAfterBoth 两个任务都执行完成后,执行下一步操作(Runnable)

import java.util.concurrent.*;

public class CompletableFutureDemo1 {
    /**
     * 定义线程池
     */
    public static ExecutorService executorService = Executors.newFixedThreadPool(3);
    public static void main(String[] args) {
        System.out.println("main start ...");
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("开启异步任务1...");
            int i = 10 / 1;
            return i;
        }, executorService);
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("开启异步任务2...");
            return "hello world!";
        }, executorService);
        future1.runAfterBothAsync(future2, () -> {
            System.out.println("开启任务3....");
        }, executorService);
        System.out.println("main end ...");
    }
}

结果:

main start ...
开启异步任务1...
开启异步任务2...
main end ...
开启任务3....

3、描述or聚合关系

(1)、applyToEither 两个任务谁执行的快,就使用那一个结果,有返回值

CompletableFuture<String> bus1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "906路";
        });

        CompletableFuture<String> bus2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "539路";
        });

        // 哪个公交车先到,就乘坐哪一辆
        CompletableFuture<String> result = bus1.applyToEither(bus2, firstBus -> "乘坐: " + firstBus);

        // 获取结果(会阻塞直到有结果)
        System.out.println(result.join()); // 输出: 乘坐: 539路 (假设539路先到)

结果:乘坐: 539路

(2)、acceptEither 两个任务谁执行的快,就消耗那一个结果,无返回值

// 创建第一个异步任务,模拟耗时 2 秒
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Result from Task 1";
        });

        // 创建第二个异步任务,模拟耗时 1 秒
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Result from Task 2";
        });

        // 使用 acceptEither:哪个任务先完成,就消费其结果
        future1.acceptEither(future2, result -> {
            System.out.println("最先完成的任务结果: " + result);
            // 这里可以执行任何消费操作,如写入日志、更新数据库等
        });

        // 主线程等待,确保异步任务完成
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("主线程结束");

结果:

最先完成的任务结果: Result from Task 2
主线程结束

(3)、runAfterEither 任意一个任务执行完成,进行下一步操作(Runnable)

// 模拟两个异步任务
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2); // 模拟耗时操作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "任务1完成";
        });

        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟耗时操作,比task1快
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "任务2完成";
        });

        // 使用 runAfterEither:当 task1 或 task2 任意一个完成时,执行通知操作
        CompletableFuture<Void> notification = task1.runAfterEither(task2, () -> {
            System.out.println("✅ 有一个任务完成了,触发通知!");
            // 这里无法获取 task1 或 task2 的返回结果
        });

        // 等待通知完成
        try {
            notification.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        System.out.println("主线程继续执行");

结果:

✅ 有一个任务完成了,触发通知!
主线程继续执行

4、并行执行

CompletableFuture类自己也提供了anyOf()和allOf()用于支持多个CompletableFuture并行执行。

anyOf的用法

// 创建两个异步任务
        CompletableFuture<String> fastTask = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟较快的响应
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Fast Result";
        });
        CompletableFuture<String> slowTask = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3); // 模拟较慢的响应
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Slow Result";
        });
        // 使用 anyOf 等待任意一个任务完成
        CompletableFuture<Object> anyDone = CompletableFuture.anyOf(fastTask, slowTask);
        // 获取最先完成的结果(需类型转换)
        Object result = null;
        try {
            result = anyDone.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        System.out.println("First completed result: " + result); // 输出: Fast Result

结果:First completed result: Fast Result

allOf的用法:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " World");
        CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
        all.thenRun(() -> {
            // 所有任务完成后的操作
            String result1 = future1.join(); // 注意,这里使用join获取结果,因为我们已经知道任务已经完成
            String result2 = future2.join();
            System.out.println(result1 + result2);
        });

结果:Hello World

注意:allOf返回的CompletableFuture<Void>在所有任务完成时完成,但不会聚合结果。我们需要分别获取每个任务的结果。

下面的代码并发地处理一个列表中的元素,为每个元素异步查询经纬度,然后设置到 VO 中,最后等待所有异步任务完成。

CompletableFuture.allOf(emergencySpaceSensitiveTargetsVOS.stream()
                    .map(vo -> CompletableFuture.runAsync(() -> {
                        try {
                            Map<String, Double> longitudeAndLatitude =
                                    riverLinePointNearUnitDao.queryLongitudeAndLatitude(vo.getPointId());
                            vo.setClosestPointLongitude(longitudeAndLatitude.get("longitude"));
                            vo.setClosestPointLatitude(longitudeAndLatitude.get("latitude"));
                        } catch (Exception e) {
                            // 建议记录日志,避免静默失败
                            log.error("Failed to query longitude/latitude for pointId: " + vo.getPointId(), e);
                        }
                    })).toArray(CompletableFuture[]::new)).join();

创建异步任务流

emergencySpaceSensitiveTargetsVOS.stream()
    .map(vo -> CompletableFuture.runAsync(() -> { ... }))

收集所有future

.toArray(CompletableFuture[]::new)

等待所有任务完成

CompletableFuture.allOf(...).join();

如果不使用异步,等价写法是:

for (EmergencySpaceSensitiveTargetsVO vo : emergencySpaceSensitiveTargetsVOS) {
    try {
        Map<String, Double> map = riverLinePointNearUnitDao.queryLongitudeAndLatitude(vo.getPointId());
        vo.setClosestPointLongitude(map.get("longitude"));
        vo.setClosestPointLatitude(map.get("latitude"));
    } catch (Exception e) {
        log.error("Failed to query longitude/latitude for pointId: " + vo.getPointId(), e);
    }
}

异步版本利用并发提升性能,适合查询操作耗时且 IO 密集型的场景。

5、处理异常

使用exceptionally方法处理异常,它相当于一个catch块,可以返回一个默认值。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            if (true) {
                throw new RuntimeException("Exception occurred!");
            }
            return "Hello";
        }).exceptionally(ex -> {
            System.out.println(ex.getMessage());
            return "Default";
        });

结果:java.lang.RuntimeException: Exception occurred!

也可以使用handle方法,它同时处理正常结果和异常。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            if (true) {
                throw new RuntimeException("Exception occurred!");
            }
            return "Hello";
        }).handle((result, ex) -> {
            if (ex != null) {
                return "Default";
            }
            return result;
        });
        try {
            String s = future.get();
            System.out.println(s);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

结果:Default

三、常用操作

1、创建异步操作

CompletableFuture 提供了四个静态方法来创建一个异步操作:

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

这四个方法区别在于:

(1)、runAsync 方法以Runnable函数式接口类型为参数,没有返回结果supplyAsync 方法以Supplier函数式接口类型为参数,返回结果类型为USupplier 接口的 get() 方法是有返回值的(会阻塞)

(2)、没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。

(3)、默认情况下 CompletableFuture 会使用公共的 ForkJoinPool 线程池,这个线程池默认创建的线程数是 CPU 的核数(我的CPU是8核)(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism=N 来设置 ForkJoinPool 线程池的线程数)。如果所有 CompletableFuture 共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰

指定VM options:

案例1:

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

public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Runnable runnable = ()-> { // lambda表达式写法,比匿名表达式写法更优
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("[" + Thread.currentThread().getName() + "]" + "执行无返回结果的异步任务");
        };
        CompletableFuture.runAsync(runnable); // 创建异步操作

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("[" + Thread.currentThread().getName() + "]" + "执行有返回值的异步任务...");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "[" + Thread.currentThread().getName() + "]" + "Hello World";
        });

        String s = future.get();
        System.out.println(s);
    }
}

结果如下:

[ForkJoinPool.commonPool-worker-2]执行有返回值的异步任务...
[ForkJoinPool.commonPool-worker-1]执行无返回结果的异步任务
[ForkJoinPool.commonPool-worker-2]Hello World

supplyAsync设置超时时间

// 3. 异步执行有返回值的任务
        CompletableFuture<String> supplyAsyncFuture = CompletableFuture.supplyAsync(() -> {
            // 模拟耗时操作
            try { Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "任务结果";
        });
        // 设置超时时间
        try {
            String result = supplyAsyncFuture.get(2, TimeUnit.SECONDS);
            System.out.println(result);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } catch (TimeoutException e) {
            throw new RuntimeException(e);
        }

结果:

image

2、获取结果

join()和get()方法都是用来获取CompletableFuture异步之后的返回值。

  • join() 方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。
  • get() 方法抛出的是经过检查的异常,ExecutionException。

InterruptedException 需要用户手动处理(抛出或者 try catch)。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
        try {
            String result = future.get(); // 抛出InterruptedException, ExecutionException
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        // 或者
        String result2 = future.join(); // 抛出CompletionException(非检查异常)

注意:get()方法会阻塞直到任务完成,如果任务异常,会抛出ExecutionException。

3、结果处理

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

(1)、Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。

(2)、方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。

(3)、这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常。

案例2:

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Function;

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (new Random().nextInt(10) % 2 == 0) {
                int i = 12 / 0;
            }

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

        future.whenComplete(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                System.out.println(s + "执行完成!");
            }
        });

        future.exceptionally(new Function<Throwable, String>() {
            @Override
            public String apply(Throwable throwable) {
                System.out.println("执行失败:" + throwable.getMessage());
                return "异常xxxx";
            }
        });
        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
    }
}

正常执行的结果:

执行结束!
test执行完成!

异常执行的结果:

执行失败:java.lang.ArithmeticException: / by zero
null执行完成!

可以改造成如下所示:

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Function;

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (new Random().nextInt(10) % 2 == 0) {
                int i = 12 / 0;
            }

            System.out.println("执行结束!");
            return "test";
        }).whenComplete(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                System.out.println(s + "执行完成!");
            }
        }).exceptionally(new Function<Throwable, String>() {
            @Override
            public String apply(Throwable throwable) {
                System.out.println("执行失败:" + throwable.getMessage());
                return "异常xxxx";
            }
        });
        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
    }
}

4、结果转换

所谓结果转换,就是将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果。

(1)、thenApply

thenApply 接收一个函数作为参数,使用该函数处理上一个CompletableFuture 调用的结果,并返回一个具有处理结果的Future对象。

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)

案例3:

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

public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture.supplyAsync(() -> {
            int result = 100;
            System.out.println("一阶段:" + result);

            return result;
        }).thenApply(number -> { // 这里的number是上个方法返回的结果
            int result = number * 3;
            System.out.println("二阶段:" + result);
            return result;
        });

        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
    }
}

结果如下:

一阶段:100
二阶段:300

thenApplyAsyn异步调用

以上回调方法(如thenApply)都是在同一个线程中执行,如果你想在另一个线程中执行回调,可以使用带Async后缀的方法,如thenApplyAsync。

CompletableFuture.supplyAsync(() -> "Hello")
        .thenApplyAsync(s -> s + " World"); // 会在另一个线程中执行

(2)、thenCompose

thenCompose 的参数为一个返回 CompletableFuture 实例的函数,该函数的参数是先前计算步骤的结果。

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;

案例四

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
public class Test5 {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(30);
            System.out.println("第一阶段:" + number);
            return number;
        }).thenCompose(new Function<Integer, CompletionStage<Integer>>() {
            @Override
            public CompletionStage<Integer> apply(Integer number) {
                return CompletableFuture.supplyAsync(new Supplier<Integer>() {

                    @Override
                    public Integer get() {
                        int number2 = number * 2;
                        System.out.println("第二阶段:" + number2);
                        return number2;
                    }
                });
            }
        });
        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
    }
}

结果如下:

第一阶段:12
第二阶段:24

thenApply 和 thenCompose的区别

(1)、thenApply 转换的是泛型中的类型,返回的是同一个CompletableFuture;

(2)、thenCompose 将内部的 CompletableFuture 调用展开来并使用上一个CompletableFutre 调用的结果在下一步的 CompletableFuture 调用中进行运算,是生成一个新的CompletableFuture。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class Test6 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> result1 = future.thenApply(param -> param + " World!");

        CompletableFuture<String> result2 =
                future.thenCompose(param -> CompletableFuture.supplyAsync(() -> param + " World!"));

        System.out.println(result1.get());
        System.out.println(result2.get());

        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
    }
}

结果如下:

Hello World!
Hello World!

5、结果消费

与结果处理和结果转换系列函数返回一个新的 CompletableFuture 不同,结果消费系列函数只对结果执行Action,而不返回新的计算值。

根据对结果的处理方式,结果消费函数又分为:

  • thenAccept系列:对单个结果进行消费
  • thenAcceptBoth系列:对两个结果进行消费
  • thenRun系列:不关心结果,只对结果执行Action

(1)、thenAccept

通过观察该系列函数的参数类型可知,它们是函数式接口Consumer,这个接口只有输入,没有返回值。

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);

示例

CompletableFuture<String> supplyAsyncFuture = CompletableFuture.supplyAsync(() -> {
            // 模拟耗时操作
            try { Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "任务结果";
        });
        // 阻塞获取结果(可能抛出异常)
        try {
            String result = supplyAsyncFuture.get();
            System.out.println("结果: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 完成后执行回调
        supplyAsyncFuture.thenAccept(result -> {
            System.out.println("接收到结果: " + result);
        });

结果:

结果: 任务结果
接收到结果: 任务结果

(2)、thenAcceptBoth

thenAcceptBoth 函数的作用是,当两个 CompletionStage 都正常完成计算的时候,就会执行提供的action消费两个异步的结果。

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);

案例

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class Test8 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> futrue1 = CompletableFuture.supplyAsync(() -> {
            int number1 = new Random().nextInt(3) + 1;
            try {
                TimeUnit.SECONDS.sleep(number1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第一阶段:" + number1);
            return number1;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            int number2 = new Random().nextInt(3) + 1;
            try {
                TimeUnit.SECONDS.sleep(number2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第二阶段:" + number2);
            return number2;
        });

        futrue1.thenAcceptBoth(future2, (number1, number2) -> System.out.println("最终结果:" + (number1 + number2)));

        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
    }
}

结果如下:

第二阶段:1
第一阶段:1
最终结果:2

(3)、thenRun

thenRun 也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun 会在上一阶段 CompletableFuture 计算完成的时候执行一个Runnable,Runnable并不使用该 CompletableFuture 计算的结果。

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);

案例

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

public class Test9 {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture.supplyAsync(()-> {
            int number = new Random().nextInt(10);
            System.out.println("第一阶段:" + number);
            return number;
        }).thenRun(()-> System.out.println("thenRun()执行..."));

        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法

    }
}

6、结果组合

(1)、thenCombine

thenCombine 方法,合并两个线程任务的结果,并进一步处理。

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);

案例

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Test10 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future1 = CompletableFuture
                .supplyAsync(() -> {
                    int number1 = new Random().nextInt(10);
                    System.out.println("第一阶段:" + number1);
                    return number1;
                });

        CompletableFuture<Integer> future2 = CompletableFuture
                .supplyAsync(() -> {
                    int number2 = new Random().nextInt(10);
                    System.out.println("第二阶段:" + number2);
                    return number2;
                });

        CompletableFuture<Integer> result = future1
                .thenCombine(future2, (number1, number2) -> number1 + number2);
        System.out.println("最终结果:" + result.get());

        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
    }
}

结果:

第一阶段:5
第二阶段:9
最终结果:14

7、任务交互

所谓线程交互,是指将两个线程任务获取结果的速度相比较,按一定的规则进行下一步处理。

(1)、applyToEither

两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的转化操作。

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);

案例

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Test11 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future1 = CompletableFuture
                .supplyAsync(() -> {
                    int number = new Random().nextInt(10);
                    System.out.println("第一阶段start:" + number);
                    try {
                        TimeUnit.SECONDS.sleep(number);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第一阶段end:" + number);
                    return number;
                });

        CompletableFuture<Integer> future2 = CompletableFuture
                .supplyAsync(() -> {
                    int number = new Random().nextInt(10);
                    System.out.println("第二阶段start:" + number);
                    try {
                        TimeUnit.SECONDS.sleep(number);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第二阶段end:" + number);
                    return number;
                });

        future1.applyToEither(future2, number -> {
            System.out.println("最快结果:" + number);
            return number * 2;
        });
        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
    }
}

结果;

第一阶段start:2
第二阶段start:9
第一阶段end:2
最快结果:2

(2)、acceptEither

两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的消费操作。

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);

案例

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Test12 {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future1 = CompletableFuture
                .supplyAsync(() -> {
                    int number = new Random().nextInt(10) + 1;
                    try {
                        TimeUnit.SECONDS.sleep(number);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第一阶段:" + number);
                    return number;
                });

        CompletableFuture<Integer> future2 = CompletableFuture
                .supplyAsync(() -> {
                    int number = new Random().nextInt(10) + 1;
                    try {
                        TimeUnit.SECONDS.sleep(number);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第二阶段:" + number);
                    return number;
                });

        future1.acceptEither(future2, number -> System.out.println("最快结果:" + number));
//        TimeUnit.SECONDS.sleep(3);//线程阻塞的方法
        countDownLatch.await();
    }

}

结果:

第一阶段:1
最快结果:1
第二阶段:9

(3)、runAfterEither

两个线程任务相比较,有任何一个执行完成,就进行下一步操作,不关心运行结果。

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);

案例

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Test13 {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> future1 = CompletableFuture
                .supplyAsync(() -> {
                    int number = new Random().nextInt(5);
                    try {
                        TimeUnit.SECONDS.sleep(number);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第一阶段:" + number);
                    return number;
                });

        CompletableFuture<Integer> future2 = CompletableFuture
                .supplyAsync(() -> {
                    int number = new Random().nextInt(5);
                    try {
                        TimeUnit.SECONDS.sleep(number);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第二阶段:" + number);
                    return number;
                });

        future1.runAfterEither(future2, () -> System.out.println("已经有一个任务完成了")).join();

        countDownLatch.await();
    }
}

结果:

第一阶段:1
已经有一个任务完成了
第二阶段:4

(4)、runAfterBoth

两个线程任务相比较,两个全部执行完成,才进行下一步操作,不关心运行结果。

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);

案例

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Test14 {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);

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

        CompletableFuture<Integer> future1 = CompletableFuture
                .supplyAsync(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第一阶段:1");
                    return 1;
                });

        CompletableFuture<Integer> future2 = CompletableFuture
                .supplyAsync(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第二阶段:2");
                    return 2;
                });

        future1.runAfterBoth(future2, () -> System.out.println("上面两个任务都执行完成了。"));

        countDownLatch.await();
    }
}

(5)、anyOf

anyOf 方法的参数是多个给定的 CompletableFuture,当其中的任何一个完成时,方法返回这个 CompletableFuture。

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

案例:

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Test15 {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Random random = new Random();
        CompletableFuture<String> future1 = CompletableFuture
                .supplyAsync(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(random.nextInt(5));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "hello";
                });

        CompletableFuture<String> future2 = CompletableFuture
                .supplyAsync(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(random.nextInt(1));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "world";
                });
        CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2);
        System.out.println(result.get());

        countDownLatch.await();
    }
}

结果:

world

(6)、allOf

allOf方法用来实现多 CompletableFuture 的同时返回。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

案例

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Test16 {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future1 = CompletableFuture
                .supplyAsync(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("future1完成!");
                    return "future1完成!";
                });

        CompletableFuture<String> future2 = CompletableFuture
                .supplyAsync(() -> {
                    System.out.println("future2完成!");
                    return "future2完成!";
                });

        CompletableFuture<Void> combindFuture = CompletableFuture
                .allOf(future1, future2);
        try {
            combindFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        countDownLatch.await();
    }
}

结果:

future2完成!
future1完成!

CompletableFuture常用方法总结

线程池配置:

// 自定义线程池
ExecutorService executor = Executors.newFixedThreadPool(10);

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 使用自定义线程池执行
    return "使用自定义线程池";
}, executor);

// 记得关闭线程池
executor.shutdown();

最佳实践

  1. 避免阻塞操作:尽量使用回调而非 get()

  2. 合理使用线程池:避免使用默认的 ForkJoinPool

  3. 处理异常:每个链式调用都应考虑异常处理

  4. 资源清理:及时关闭自定义线程池

  5. 超时设置:使用 completeOnTimeout() 或 orTimeout()

CompletableFuture 提供了强大的异步编程能力,通过链式调用和组合操作,可以构建复杂的异步处理流程。掌握其基本用法和异常处理机制,能显著提升 Java 异步编程的效率和质量。

四、For循环中使用CompletableFuture

有时候,我们需要在某个接口中,远程调用第三方的某个接口。

比如:在注册企业时,需要调用天眼查接口,查一下该企业的名称和统一社会信用代码是否正确。

这时候在企业注册接口中,不得不先调用天眼查接口校验数据。如果校验失败,则直接返回。如果校验成功,才允许注册。

如果只是一个企业还好,但如果某个请求有10个企业需要注册,是不是要在企业注册接口中,循环调用10次天眼查接口才能判断所有企业是否正常呢?

public void register(List<Corp> corpList) {
  for(Corp corp: corpList) {
      CorpInfo info = tianyanchaService.query(corp);  
      if(null == info) {
         throw new RuntimeException("企业名称或统一社会信用代码不正确");
      }
  }
  doRegister(corpList);
}

这样做可以,但会导致整个企业注册接口性能很差,极容易出现接口超时问题。

那么,如何解决这类在循环中调用远程接口的问题呢?

1、批量操作

远程接口支持批量操作,比如天眼查支持一次性查询多个企业的数据,这样就无需在循环中查询该接口了。

但实际场景中,有些第三方不愿意提供第三方接口。

2、并发操作

java8以后通过CompletableFuture类,实现多个线程查天眼查接口,并且把查询结果统一汇总到一起。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureFor {
    public static void main(String[] args) {
        //循环调用方法
        List<CompletableFuture<?>> futures = new ArrayList<>();
        for(int y = 1;y < 10;y++){
            futures.add(CompletableFuture.supplyAsync(() ->{
                try {
                    Thread.sleep(1000);
                    List<String> result = query(y,1000);
                    return result;
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }));
        }
        

        long start = System.currentTimeMillis();
        List<CompletableFuture<?>> futures = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            int finalI = i;
                futures.add(CompletableFuture.supplyAsync(() ->{
                    try {
                        Thread.sleep(1000);
                        System.out.println("线程:CompletableFuture" + Thread.currentThread().getName());
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return finalI;
                }));
        }
        //等待全部完成    
        CompletableFuture.allOf(futures.toArray(newCompletableFuture[0])).join();
        //获取内容    
        for (CompletableFuture future : futures) {
            try {
                Object s = future.get();
                System.out.println(s);
            }
            catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("主线程:" + Thread.currentThread().getName());
        System.out.println("cost" + (end - start));
    }
}

案例

    @Asyncpublic CompletableFuture<Integer> init() {
        int c = 0;
        List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT DISTINCT CODE FROM TAB1 ");
        List<CompletableFuture<Integer>> futures = new ArrayList<>();
        for(int i=0;i<list.size();i++) {
            CompletableFuture<Integer> future1 = lineService.initTask(list.get(i).get("CODE").toString());
            futures.add(future1);
        }
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); // allOf()用于支持多个CompletableFuture并行执行
        allFutures.join();  // 等待全部完成
        for(CompletableFuture<Integer> future : futures){
            try {
                int k = future.get();
                c += k;
            } catch (InterruptedException | ExecutionException e) {

            }
        }
        return CompletableFuture.completedFuture(c);
    }

 

posted on 2023-06-21 11:18  周文豪  阅读(3943)  评论(1)    收藏  举报