MST

星途 面试题库

面试题:Java CompletableFuture如何优化高并发场景下的资源利用

在一个高并发场景中,有大量的CompletableFuture异步任务需要执行,这些任务可能会竞争有限的系统资源(如线程池、数据库连接等)。请阐述如何通过合理使用CompletableFuture的特性(如自定义线程池、任务编排等)来优化资源利用,避免资源耗尽,并提高系统的整体性能和稳定性。请结合具体的代码示例和理论依据进行说明。
32.6万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 自定义线程池

在高并发场景下,使用默认的线程池可能无法满足需求,通过自定义线程池可以更好地控制资源。

  • 理论依据:默认的线程池(如ForkJoinPool.commonPool())可能会被多个异步任务共享,在高负载下容易出现资源竞争。自定义线程池可以根据任务特点调整线程数量、队列容量等参数,提高资源利用率。
  • 代码示例
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class CompletableFutureCustomThreadPoolExample {
    private static final AtomicInteger counter = new AtomicInteger();
    private static final ExecutorService executor = new ThreadPoolExecutor(
            5,
            10,
            10L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100),
            r -> new Thread(r, "CustomThread-" + counter.incrementAndGet())
    );

    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            // 模拟任务
            System.out.println("Task running in custom thread pool");
            return "Result";
        }, executor)
       .thenAccept(result -> System.out.println("Result received: " + result))
       .whenComplete((res, ex) -> executor.shutdown());
    }
}

在上述代码中,创建了一个自定义的ThreadPoolExecutor,指定了核心线程数为5,最大线程数为10,线程存活时间为10秒,任务队列容量为100,并为每个线程设置了自定义的名称。通过supplyAsync方法将任务提交到自定义线程池执行。

2. 任务编排

合理编排任务可以避免不必要的资源浪费,提高系统性能。

  • 理论依据:通过thenApplythenComposethenAccept等方法对任务进行编排,可以按照逻辑顺序执行任务,避免所有任务同时竞争资源。例如,有些任务可能需要依赖其他任务的结果,通过链式调用可以确保在依赖任务完成后再执行后续任务。
  • 代码示例
import java.util.concurrent.*;

public class CompletableFutureChainingExample {
    private static final ExecutorService executor = Executors.newFixedThreadPool(5);

    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            System.out.println("Task1 starts");
            return "Task1 result";
        }, executor)
       .thenApply(result1 -> {
            System.out.println("Task2 starts with result: " + result1);
            return "Task2 result";
        })
       .thenAccept(result2 -> System.out.println("Task2 result received: " + result2))
       .whenComplete((res, ex) -> executor.shutdown());
    }
}

在这个示例中,Task2依赖于Task1的结果,通过thenApply方法将Task1的结果传递给Task2,保证了任务的顺序执行,减少了资源的无效竞争。

3. 批量任务处理与资源控制

对于大量的异步任务,可以采用批量处理的方式来控制资源。

  • 理论依据:如果一次性提交过多任务,可能会瞬间耗尽资源。将任务分成多个批次,每次提交一定数量的任务,待部分任务完成释放资源后再提交下一批任务,可以有效避免资源耗尽。
  • 代码示例
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

public class CompletableFutureBatchProcessingExample {
    private static final ExecutorService executor = Executors.newFixedThreadPool(5);

    public static void main(String[] args) {
        List<Integer> taskIds = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            taskIds.add(i);
        }
        int batchSize = 5;
        for (int i = 0; i < taskIds.size(); i += batchSize) {
            List<Integer> batch = taskIds.subList(i, Math.min(i + batchSize, taskIds.size()));
            List<CompletableFuture<String>> futures = batch.stream()
                   .map(id -> CompletableFuture.supplyAsync(() -> {
                        System.out.println("Task " + id + " is running");
                        return "Task " + id + " result";
                    }, executor))
                   .collect(Collectors.toList());
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                   .thenRun(() -> System.out.println("Batch completed"));
        }
        executor.shutdown();
    }
}

在上述代码中,将20个任务分成每批5个任务进行处理。通过CompletableFuture.allOf方法等待一批任务全部完成后再进行下一批任务的提交,有效控制了资源的使用。

通过合理利用CompletableFuture的这些特性,能够优化资源利用,避免资源耗尽,提高系统在高并发场景下的性能和稳定性。