1..5java面试题:线程池
线程池是 Java 面试中必考且最能拉开差距的知识点。老练的 Java 工程师不仅能讲清楚参数还能结合源码执行流程、生产调优经验、监控与坑点进行深入阐述。下面我用“核心原理 → 参数拆解 → 工作流程 → 实战案例 → 调优与监控 → 常见陷阱”这条线帮你彻底吃透。一、为什么必须用线程池降低资源消耗复用已创建的线程减少线程创建、销毁的开销。提高响应速度任务到达时无需等待线程创建即可立即执行。提高线程的可管理性统一分配、监控和调优防止无限制创建线程导致 OOM。提供更强大的执行控制执行、排队、拒绝策略、定时执行等。二、线程池的核心构造参数ThreadPoolExecutorpublicThreadPoolExecutor(intcorePoolSize,// 核心线程数intmaximumPoolSize,// 最大线程数longkeepAliveTime,// 空闲线程存活时间TimeUnitunit,// 时间单位BlockingQueueRunnableworkQueue,// 任务队列ThreadFactorythreadFactory,// 线程工厂RejectedExecutionHandlerhandler)// 拒绝策略2.1 核心参数详解corePoolSize常驻线程数即使空闲也不回收除非allowCoreThreadTimeOut(true)。maximumPoolSize线程池允许的最大线程数。线程数 core 当队列满后额外创建的线程但总量 ≤ max。keepAliveTime unit超出 core 的线程如果空闲超过此时间会被回收。workQueue存储等待执行的任务是线程池吞吐量的关键。threadFactory自定义线程命名、守护、优先级等便于监控排错。handler当线程数到达 max 且队列满时对新任务的拒绝策略。三、线程池的工作流程源码级理解当提交一个任务execute(Runnable command)时当前线程数 corePoolSize→ 直接创建新线程执行任务即使有空闲核心线程也会优先创建达到 core。当前线程数 ≥ corePoolSize→ 尝试将任务放入 workQueue 排队。队列已满→ 创建新线程非 core执行任务直到达到 maximumPoolSize。线程数 max 且队列满→ 执行拒绝策略。面试官追问细节为什么核心线程满了不立即创建非核心线程而是先入队答为了缓冲突发流量减少线程创建销毁的开销除非队列设定为容量极小的如 SynchronousQueue则不等。什么时候会回收非核心线程答当getPoolSize() corePoolSize且空闲超过keepAliveTime回收直到线程数回到 core。四、阻塞队列选型与实战案例4.1 常见队列对比队列结构容量适用场景SynchronousQueue无存储一对一交接0请求量平稳把任务直接交给线程拒绝策略容易触发LinkedBlockingQueue链表Integer.MAX_VALUE默认固定线程数 无界缓冲但可能 OOMArrayBlockingQueue数组必须指定容量有限缓冲配合有界队列 拒绝策略更安全DelayQueue优先级堆无界延时任务调度PriorityBlockingQueue优先级堆无界按优先级执行需任务实现Comparable血泪教训Executors.newFixedThreadPool(10)底层用的是LinkedBlockingQueue无界队列队列无限增长会 OOM。所以生产严禁直接使用 Executors 的四个工厂方法。4.2 案例一自定义线程池安全高效ThreadPoolExecutorpoolnewThreadPoolExecutor(4,// core8,// max60L,TimeUnit.SECONDS,// 空闲回收newArrayBlockingQueue(200),// 有界队列防止 OOMnewThreadFactory(){privatefinalAtomicIntegercountnewAtomicInteger(1);OverridepublicThreadnewThread(Runnabler){ThreadtnewThread(r,order-pool-count.getAndIncrement());t.setDaemon(false);// 非守护确保任务执行完returnt;}},newThreadPoolExecutor.CallerRunsPolicy()// 拒绝策略交给主线程执行防止丢任务);五、拒绝策略与场景选择策略行为适用场景AbortPolicy默认抛RejectedExecutionException必须通知上游记录异常CallerRunsPolicy由提交任务的线程执行防丢任务可降低流量DiscardPolicy静默丢弃新任务不重要的日志、统计DiscardOldestPolicy丢弃队列头部最旧任务重试提交只保留最新数据如实时性高的任务自定义策略实现RejectedExecutionHandler可记录日志、告警、降级到 MQ 等需要精细控制时案例自定义拒绝策略 告警publicclassAlertRejectedHandlerimplementsRejectedExecutionHandler{OverridepublicvoidrejectedExecution(Runnabler,ThreadPoolExecutorexecutor){log.error(线程池任务被拒绝当前活跃线程: {}, 队列大小: {},executor.getActiveCount(),executor.getQueue().size());// 尝试重新入队列或降级到 MQ// 如果仍失败则丢弃或抛异常}}六、如何合理设置线程数6.1 经典公式CPU 密集型线程数 CPU 核心数 1例加密解密、复杂计算。I/O 密集型线程数 CPU 核心数 * 2 或线程数 CPU核心数 * (1 平均等待时间/平均处理时间)例数据库查询、网络调用。混合型拆分成两个线程池根据任务耗时比例分配。6.2 实际案例在订单处理微服务中既有 CPU 密集的规则计算也有大量 DB 和远程调用。我们拆分两个池计算池core Ncpu,max Ncpu2,SynchronousQueue。业务池core 10,max 50,ArrayBlockingQueue(500)处理大部分 I/O 操作。最终都需要压测验证公式只是起点。七、线程池的监控生产必备7.1 指标采集通过ThreadPoolExecutor提供的 getter 方法暴露给 Prometheus 或日志。publicvoidprintPoolStats(ThreadPoolExecutorpool){log.info(核心线程数: {}, 最大线程数: {}, 当前线程数: {}, 活跃线程数: {}, 队列中任务数: {}, 已完成任务数: {}, 拒绝任务数: {},pool.getCorePoolSize(),pool.getMaximumPoolSize(),pool.getPoolSize(),pool.getActiveCount(),pool.getQueue().size(),pool.getCompletedTaskCount(),// 需要自定义包装才能获取拒绝计数);}7.2 监控指标面板队列积压队列中等待任务数持续上升说明消费能力不足需扩容。活跃线程占比长期接近 max且队列不空考虑增加 max 或提升机器。拒绝次数发生拒绝说明线程池容量不足或下游出了问题。7.3 实战案例动态调整线程池使用 Apollo/Nacos 等配置中心动态更新 core/max结合setCorePoolSize()和setMaximumPoolSize()实时生效。八、线程池的优雅关闭publicvoidshutdownGracefully(ThreadPoolExecutorpool){pool.shutdown();// 不再接收新任务已提交的任务继续执行try{if(!pool.awaitTermination(60,TimeUnit.SECONDS)){pool.shutdownNow();// 尝试中断所有任务if(!pool.awaitTermination(30,TimeUnit.SECONDS)){log.error(线程池未关闭成功部分任务可能丢失);}}}catch(InterruptedExceptione){pool.shutdownNow();Thread.currentThread().interrupt();}}注意shutdownNow不保证能停止正在执行的任务只是尝试中断业务代码需响应中断。九、常见陷阱与血泪史用 Executors 创建线程池导致 OOMnewFixedThreadPool/newSingleThreadExecutor无界队列newCachedThreadPool最大线程数为Integer.MAX_VALUE都危险。线程池中线程的异常被吞掉execute提交的Runnable异常会导致线程消亡任务丢失必须用submitFuture.get()或自定义UncaughtExceptionHandler。t.setUncaughtExceptionHandler((thread,ex)-log.error(线程异常,ex));submit后不get异常被吞submit返回的Future如果不调用get且任务抛出异常你完全不知道。线程池里用 ThreadLocal线程复用导致值污染必须在任务结束时remove()。tomcat / web 容器共享线程池误区不要随意使用 servlet 容器线程池处理长耗时任务应自定义线程池剥离。十、面试串联话术建议背诵“线程池的核心是一个ThreadPoolExecutor我理解它的工作流程是优先创建核心线程、满核心入队列、队列满开最大线程、最后拒绝。生产上我绝对不用Executors的快捷方法而是用ArrayBlockingQueue做有界缓冲、自定义ThreadFactory命名线程、CallerRunsPolicy或自定义拒绝策略保证任务不丢失。线程数设置上CPU 密集型用CPU核数1I/O 密集型适当放大并压测验证。监控方面我会定期采集队列大小、活跃线程数、拒绝次数到 Prometheus 并配置告警。实际项目中我遇到过因LinkedBlockingQueue无界导致 OOM 的事故后来全部替换为有界队列并动态调整参数。也踩过submit异常被吞、ThreadLocal残留的坑所以现在任务异常全部捕获记录线程工厂设置UncaughtExceptionHandler并在 finally 块清理ThreadLocal。”这套话术结合了理论、源码流程、调优和真坑能让面试官确认你是真正的线程池专家。针对Javajava.util.concurrent包的标准实现线程池的核心参数和创建方式如下。⚙️ 核心参数共7个ThreadPoolExecutor的构造函数包含以下7个核心参数corePoolSize核心线程数线程池中一直保持存活的线程数量即使空闲。如果允许超时核心线程也可能被回收。maximumPoolSize最大线程数线程池中允许的最大线程数量。keepAliveTime存活时间当实际线程数超过核心线程数时多余的空闲线程在被终止前等待新任务的最大时间。unit时间单位keepAliveTime的时间单位如TimeUnit.SECONDS。workQueue工作队列用于存储等待执行任务的阻塞队列。关键联动当核心线程都在忙时新任务会进入队列。只有当队列填满后才会继续创建新线程直到maximumPoolSize。threadFactory线程工厂用于创建新线程的工厂通常用于设置线程名称、优先级或守护进程状态。默认使用Executors.defaultThreadFactory()。rejectedExecutionHandler拒绝策略当线程池已关闭或队列已满且线程数已达到最大值时对新任务采取的处理策略。内置策略AbortPolicy默认抛异常、CallerRunsPolicy调用者运行、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃最老任务。 如何创建线程池通常推荐直接使用ThreadPoolExecutor构造方法而不是使用Executors工具类后者容易导致OOM。1. 推荐方式自定义参数生产环境标配importjava.util.concurrent.*;publicclassThreadPoolDemo{publicstaticvoidmain(String[]args){// 1. 创建线程池实例ThreadPoolExecutorexecutornewThreadPoolExecutor(5,// corePoolSize10,// maximumPoolSize60L,// keepAliveTimeTimeUnit.SECONDS,// unitnewArrayBlockingQueue(100),// workQueue有界队列Executors.defaultThreadFactory(),// threadFactorynewThreadPoolExecutor.AbortPolicy()// rejectionHandler);// 2. 提交任务这是“用线程池创建线程”执行任务的标准方式for(inti0;i15;i){inttaskIdi;executor.execute(()-{System.out.println(线程 Thread.currentThread().getName() 执行任务taskId);// 模拟业务逻辑});}// 3. 关闭线程池不再接收新任务executor.shutdown();}}2. 快捷方式Executors工具类仅限特定场景虽然不推荐全局使用但了解其底层参数有助于理解Executors.newFixedThreadPool(5)固定线程数。corePoolSizemaxPoolSize5队列为无界的LinkedBlockingQueue可能导致内存溢出。Executors.newCachedThreadPool()缓存线程池。corePoolSize0maxPoolSizeInteger.MAX_VALUE队列为SynchronousQueue线程数可能无限增长。Executors.newSingleThreadExecutor()单线程池。corePoolSizemaxPoolSize1无界队列。⚠️ 核心避坑指南队列与最大线程数的联动请务必记住规则——只有当workQueue满了才会触发corePoolSize向maximumPoolSize扩容。因此如果使用无界队列如默认的LinkedBlockingQueuemaximumPoolSize参数将形同虚设。合理设置拒绝策略生产环境慎用AbortPolicy默认抛异常可能中断业务流程通常建议使用CallerRunsPolicy或自定义策略实现削峰填谷。线程命名强烈建议自定义ThreadFactory设置带业务前缀的线程名称如order-pool-thread-1以便排查问题。如果你想深入了解如何根据CPU密集型和IO密集型任务来推算具体参数值我可以为你详细展开。需要吗