MST

星途 面试题库

面试题:Java 中 CompletableFuture 如何优化简单异步任务的执行效率

假设你有一个需要异步执行的任务,例如从数据库读取数据并进行简单处理。请使用 CompletableFuture 实现该异步任务,并阐述如何通过合理的线程池配置以及 CompletableFuture 的方法调用来提升执行效率。
34.7万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试
  1. 使用CompletableFuture实现异步任务
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class AsyncTaskWithCompletableFuture {
        private static final ExecutorService executorService;
        static {
            AtomicInteger threadNumber = new AtomicInteger(1);
            executorService = Executors.newFixedThreadPool(10, r -> {
                Thread t = new Thread(r);
                t.setName("CustomThreadPool-" + threadNumber.getAndIncrement());
                return t;
            });
        }
    
        public static CompletableFuture<String> asyncTask() {
            return CompletableFuture.supplyAsync(() -> {
                // 模拟从数据库读取数据
                System.out.println("从数据库读取数据...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                String data = "读取到的数据";
                // 简单处理数据
                String processedData = data + " 已处理";
                return processedData;
            }, executorService);
        }
    
        public static void main(String[] args) {
            asyncTask().thenAccept(System.out::println).join();
            executorService.shutdown();
        }
    }
    
  2. 通过合理的线程池配置以及CompletableFuture的方法调用来提升执行效率
    • 线程池配置
      • 线程池大小:根据任务类型和硬件资源来确定线程池大小。对于I/O密集型任务(如数据库读取),线程池大小可以设置得相对较大,因为线程在等待I/O操作完成时可以释放CPU资源。例如,如果任务主要是从数据库读取数据,并且服务器有多个CPU核心,可以设置线程池大小为CPU核心数的2 - 3倍。在上述代码中,我们创建了一个固定大小为10的线程池,这是一个可以根据实际情况调整的参数。
      • 线程命名:为线程池中的线程命名有助于在日志和监控中更好地识别线程,如上述代码中通过AtomicInteger为线程命名。
    • CompletableFuture方法调用
      • supplyAsync:用于异步执行有返回值的任务。它接受一个Supplier和一个Executor作为参数,这样可以将任务提交到指定的线程池执行,避免使用默认的ForkJoinPool.commonPool(),因为默认线程池可能在高负载下出现性能问题。
      • thenApplythenAcceptthenRun等后续处理方法:这些方法可以在异步任务完成后,以同步或异步的方式进行后续操作。例如thenApply用于对异步任务的返回值进行转换,thenAccept用于消费返回值,thenRun用于执行无参数的后续操作。合理使用这些方法可以减少不必要的线程切换和等待时间。例如,如果后续操作不需要返回值,使用thenAcceptthenRun可以避免创建额外的CompletableFuture对象。
      • thenCompose:当一个CompletableFuture的结果需要作为另一个CompletableFuture的输入时,使用thenCompose可以避免嵌套的CompletableFuture,使代码更简洁,同时也能提高性能,因为它可以更好地管理异步操作的执行顺序。
      • allOfanyOfallOf用于等待所有给定的CompletableFuture完成,anyOf用于等待任意一个CompletableFuture完成。如果有多个独立的异步任务需要执行,可以使用allOf将它们合并,然后在所有任务完成后进行统一处理,这样可以充分利用多线程并行执行的优势,提高整体效率。