
1) 【一句话结论】
银行后端处理高并发请求时,线程池设计需针对I/O密集型任务(如转账),核心线程数匹配数据库连接数或网络I/O并发数(如核心线程数=数据库连接池大小×0.8),最大线程数为核心线程数的1.5-2倍,空闲线程存活时间设为30秒左右,队列采用有界阻塞队列(容量根据系统内存计算),饱和策略根据任务关键性选择AbortPolicy(关键交易)或CallerRunsPolicy(非关键任务),确保资源高效利用且任务不丢失。
2) 【原理/概念讲解】
线程池的核心是复用线程,减少线程创建与销毁的开销。对于银行转账这类I/O密集型任务(线程主要等待数据库或网络I/O,而非CPU计算),核心参数设置需结合I/O资源:
3) 【对比与适用场景】
| 策略名称 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|
| AbortPolicy | 直接抛RejectedExecutionException | 强制拒绝任务,抛异常 | 不可容忍任务丢失(如关键转账、支付) | 会中断调用方,需捕获异常处理 |
| CallerRunsPolicy | 调用执行器线程运行任务 | 任务延迟执行,不丢失 | 任务可延迟(如非关键日志、通知) | 可能阻塞调用方线程,影响响应 |
| DiscardPolicy | 直接丢弃任务 | 任务丢失,不抛异常 | 任务可丢失(如非关键统计、日志) | 风险高,可能导致业务数据丢失 |
| DiscardOldestPolicy | 丢弃队列最老任务,执行新任务 | 保持队列有序,任务不丢失 | 任务有时间顺序要求(如订单处理、交易记录) | 可能丢弃重要历史任务,需谨慎 |
4) 【示例】
伪代码展示银行转账任务处理:
public class BankTransferService {
// 假设数据库连接池大小为100
private static final int CORE_POOL_SIZE = 80; // 数据库连接池的80%
private static final int MAX_POOL_SIZE = 120; // 核心线程数的1.5倍
private static final long KEEP_ALIVE_TIME = 30; // 秒
private static final int QUEUE_CAPACITY = 500; // 根据内存计算
private static final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
private static final ThreadFactory factory = new NamedThreadFactory("bank-transfer-pool");
private static final RejectedExecutionHandler handler = new AbortPolicy(); // 关键交易用AbortPolicy
private static final ExecutorService executor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
queue, factory, handler
);
public void processTransfer(TransferRequest request) {
executor.execute(() -> {
try {
validateRequest(request);
debit(request.getFromAccount());
credit(request.getToAccount());
logTransfer(request);
} catch (Exception e) {
logError(e, request);
}
});
}
}
5) 【面试口播版答案】
在银行后端处理高并发请求时,线程池设计需针对I/O密集型任务(如转账),核心参数设置要匹配系统资源。比如核心线程数设为数据库连接池大小的80%(假设连接池100,核心线程80),最大线程数是核心线程的1.5倍(120),空闲线程存活30秒。队列用有界阻塞队列,容量根据内存计算(比如500),防止内存溢出。饱和策略上,关键转账用AbortPolicy,直接抛异常让调用方处理;非关键任务用CallerRunsPolicy,延迟执行不丢失。这样既利用资源,又控制资源,确保系统在高并发下稳定运行,任务不丢失。
6) 【追问清单】
7) 【常见坑/雷区】