面试题答案
一键面试分析过程
- 核心线程数:处理大量用户请求且需并行调用远程服务,核心线程数应根据CPU核心数及远程服务响应时间波动情况调整。若远程服务响应快且机器CPU资源充足,核心线程数可设为接近CPU核心数,以充分利用CPU多核能力;若远程服务响应慢且波动大,核心线程数需适当增加,保证有足够线程处理请求,避免线程频繁创建销毁带来的开销。
- 最大线程数:受限于系统资源(如内存)。当请求量剧增,超过核心线程数处理能力时,若最大线程数过小,会导致部分请求排队等待时间过长;若过大,会消耗过多系统资源甚至导致系统崩溃。需通过压测确定系统能承受的最大并发线程数。
- 队列容量:用于缓冲超出核心线程数的请求。若队列容量过小,请求容易被拒绝;若过大,会导致请求在队列中等待时间过长,增加整体响应时间。若远程服务响应时间波动大,队列容量不宜过大,防止大量请求堆积。
配置建议
- 核心线程数:可先按公式
N = CPU核心数 * (1 + 平均远程服务等待时间 / 平均远程服务计算时间)
估算,然后通过压测调整。例如,CPU为8核,平均远程服务等待时间为100ms,平均计算时间为50ms,则N = 8 * (1 + 100 / 50) = 24
。 - 最大线程数:在核心线程数基础上,根据系统资源情况逐步增加并进行压测,找到系统性能最佳且资源利用率合理的数值。如在上述例子基础上,先设为核心线程数的1.5倍即36,然后压测调整。
- 队列容量:结合核心线程数和最大线程数,初始设为核心线程数的2 - 3倍,如设为48 - 72,再通过压测调整。
代码实现
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureThreadPoolExample {
private static final AtomicInteger COUNTER = new AtomicInteger(1);
public static void main(String[] args) {
// 自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
24, // 核心线程数
36, // 最大线程数
10, // 线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(60), // 队列容量
r -> new Thread(r, "MyThread-" + COUNTER.getAndIncrement()),
new ThreadPoolExecutor.CallerRunsPolicy());
// 将自定义线程池应用到CompletableFuture
CompletableFuture.supplyAsync(() -> {
// 模拟远程服务调用
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Result from remote service";
}, executor)
.thenAccept(System.out::println)
.join();
executor.shutdown();
}
}
在上述代码中,通过 ThreadPoolExecutor
创建自定义线程池,并将其作为参数传递给 CompletableFuture.supplyAsync
方法,从而将自定义线程池应用到CompletableFuture中。