Java线程池原理与面试高频问题解析
1. 线程池面试题精选从原理到实战的深度解析作为Java并发编程的核心组件线程池几乎出现在所有中高级Java开发岗位的面试中。我整理了15年面试官经验中最常被问及的20个线程池相关问题并附上深度解析和实战建议。无论你是准备面试还是想系统掌握线程池这份指南都能帮你避开90%的坑。2. 线程池基础原理剖析2.1 为什么需要线程池直接创建线程的三大致命伤创建/销毁开销大涉及操作系统级资源分配无限制创建会导致系统资源耗尽实测单个Java进程创建数万线程就会OOM缺乏统一管理线程状态监控、任务队列等线程池通过复用线程、任务队列和拒绝策略三大机制完美解决这些问题。以Tomcat默认线程池为例200个线程就能轻松应对上万并发请求。2.2 核心参数详解ThreadPoolExecutor( int corePoolSize, // 常驻核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲线程存活时间 TimeUnit unit, // 时间单位 BlockingQueueRunnable workQueue, // 任务队列 RejectedExecutionHandler handler // 拒绝策略 )关键参数组合效果当任务数 corePoolSize立即创建新线程执行当corePoolSize满且队列未满任务入队当队列满且线程数 maximumPoolSize创建新线程当线程数达到maximumPoolSize且队列满触发拒绝策略3. 高频面试题精讲3.1 线程池执行流程的陷阱题现有线程池配置corePoolSize5, maxPoolSize10, queueCapacity100。当连续提交150个任务时会发生什么标准答案前5个任务创建5个核心线程立即执行第6-105个任务进入队列100个容量第106-115个任务触发创建额外线程共10个线程第116-150个任务触发拒绝策略注意90%的候选人会忽略队列满后才创建非核心线程这一关键机制3.2 四种拒绝策略对比策略类行为适用场景AbortPolicy直接抛出RejectedExecutionException需要明确感知拒绝的批处理任务CallerRunsPolicy由提交任务的线程直接执行不希望丢失任务的Web应用DiscardPolicy静默丢弃被拒绝任务监控类等可丢失任务DiscardOldestPolicy丢弃队列最老任务并重试实时性要求高的场景实战建议电商秒杀系统推荐使用CallerRunsPolicy因为保证不会丢失订单请求提交线程通常是Tomcat线程执行时天然限流4. 线程池的实战坑点4.1 线程池死锁问题典型场景ExecutorService pool Executors.newSingleThreadExecutor(); pool.submit(() - { FutureString future pool.submit(() - inner task); return future.get(); // 死锁 });根本原因外层任务占用唯一线程内层任务在队列等待形成循环依赖。解决方案使用更大的线程池避免在任务中提交嵌套任务改用CompletionService4.2 资源泄漏问题常见于忘记调用shutdown()导致线程无法回收任务抛出未捕获异常导致工作线程退出诊断技巧// 监控线程池活跃度 ((ThreadPoolExecutor)pool).getActiveCount(); // 查看任务队列积压 ((ThreadPoolExecutor)pool).getQueue().size();5. 高级优化策略5.1 动态调参实战Spring提供的灵活调整方案Autowired private ThreadPoolTaskExecutor executor; // 运行时调整核心参数 executor.setCorePoolSize(20); executor.setQueueCapacity(200);配合监控指标当队列持续满时增大poolSize当线程空闲率70%时缩小poolSize5.2 上下文传递方案跨线程传递MDC日志标识的两种方式// 方案1装饰Runnable MDC.put(traceId, 123); executor.execute(MdcUtils.wrap(task)); // 方案2重写ThreadPoolExecutor class MdcThreadPool extends ThreadPoolExecutor { protected void beforeExecute(Thread t, Runnable r) { MDC.setContextMap(((MdcRunnable)r).getContext()); } }6. 面试加分项6.1 ForkJoinPool原理与ThreadPoolExecutor的关键区别采用工作窃取Work-Stealing算法每个线程维护独立双端队列适合计算密集型任务示例快速排序任务拆分class SortTask extends RecursiveAction { protected void compute() { if(数组足够小) 直接排序; else { 拆分左右两部分; invokeAll(new SortTask(left), new SortTask(right)); } } }6.2 CompletableFuture技巧链式调用示例CompletableFuture.supplyAsync(() - queryFromDB(), pool) .thenApplyAsync(data - process(data), pool) .exceptionally(ex - fallbackResult());注意事项默认使用ForkJoinPool.commonPool()重要业务务必指定自定义线程池7. 最佳实践总结创建线程池必须使用ThreadPoolExecutor构造方法禁止Executors快捷方法避免无界队列风险IO密集型任务建议poolSize CPU核数 * (1 平均等待时间/计算时间)监控指标至少包含活跃线程数、队列大小、拒绝次数线程池销毁时应先shutdown()再awaitTermination()最后分享一个诊断工具# 查看Java进程线程状态 jstack pid | grep pool -A 10