在Java并发面试中,线程池绝对是“高频重难点”——从核心参数设置到拒绝策略选择,几乎每家公司(众安、猎上网、小硕科技、游族网络)都会追问。很多人要么记混“线程创建顺序”,要么核心线程数乱设,甚至不知道拒绝策略要分场景选。今天就拆解3道线程池高频题,帮你理清逻辑、避开坑,面试答得专业又透彻。
第一题:线程池有哪些核心参数?什么时候会创建新线程?(猎上网/众安必问)这道题是“入门题”,但90%的人只会背参数名,说不出“核心线程满了之后,是先放队列还是先创临时线程”,更讲不清execute方法的底层逻辑。
先明确:ThreadPoolExecutor的6个核心参数(表格记,面试直接答)核心参数
作用说明
关键细节(面试必提)
corePoolSize
核心线程数(常驻线程,空闲不销毁)
除非设allowCoreThreadTimeOut=true,否则核心线程不会超时销毁
maximumPoolSize
最大线程数(核心+临时线程总数)
临时线程数 = 最大线程数 - 核心线程数
keepAliveTime
临时线程空闲存活时间
超过这个时间,临时线程会被回收
workQueue
任务阻塞队列
核心线程满时,任务先放这里(如LinkedBlockingQueue)
threadFactory
线程创建工厂
自定义线程名(如“order-thread-1”),方便排查问题
handler
拒绝策略
队列满+线程数达最大时,触发的任务处理逻辑
关键补充:什么时候创建新线程?(execute方法逻辑)线程池不是“有任务就创线程”,而是按固定顺序判断,记住4步:
判断核心线程是否满:若当前线程数 < corePoolSize,立即创建核心线程处理任务(哪怕有空闲核心线程,也会建新的,保证核心线程数满);判断队列是否满:若核心线程满了,把任务放入workQueue;判断最大线程是否满:若队列满了,且当前线程数 < maximumPoolSize,创建临时线程处理任务;触发拒绝策略:若队列满+线程数达最大,执行handler的拒绝逻辑。举个例子:核心线程5,最大线程10,队列容量20。任务来了先创5个核心线程,再放20个任务到队列,队列满了再创5个临时线程,最后任务还多就拒绝。
踩坑点提醒(90%人会错)❌ 错误逻辑:“核心线程满了就创临时线程”——跳过了“放队列”步骤!实际是先放队列,队列满了才创临时线程,这点和很多人的直觉相反;
❌ 忽略threadFactory:不自定义线程名,线上排查“线程泄露”时,看到“pool-1-thread-1”根本不知道是哪个业务的线程池;
❌ 误解allowCoreThreadTimeOut:以为核心线程永远不销毁,其实设为true后,核心线程也会超时回收(适合业务低谷时节省资源)。
第二题:核心线程数怎么设?CPU密集和IO密集场景有区别吗?(众安二面/小硕科技问过)这道题考“工程落地能力”,很多人随便设个“10”“20”,却不知道要结合业务场景——“CPU密集设核数+1,IO密集看耗时比”,还有阿姆达尔定律(衡量并行优化效果)。
正确解答:分2种场景,用公式+例子算1. CPU密集型任务(如计算、排序)核心逻辑:线程数太多会导致“上下文切换”开销激增,反而变慢;计算公式:核心线程数 = CPU核数 + 1;例子:4核CPU→设5个核心线程。多1个线程是为了应对“偶尔的内存页失效、线程阻塞”,避免CPU空闲。2. IO密集型任务(如DB查询、网络请求)核心逻辑:线程大部分时间在等IO(如等DB返回、等接口响应),需要更多线程“占满CPU”;计算公式:核心线程数 = CPU核数 × [1 +(IO耗时 / CPU耗时)];例子:4核CPU,IO耗时100ms,CPU耗时10ms→4×(1+100/10)=44个核心线程。这样在100ms的IO等待期,CPU能处理44个线程的“CPU计算部分”。3. 兜底:用阿姆达尔定律验证如果业务里“串行代码占比高”(比如必须单线程处理的逻辑),就算线程数再多,加速比也上不去。比如串行占比20%,就算线程数无限多,最大加速比也只有5倍(1/(0.2+0.8/∞)=5),这时不用设太多线程。
踩坑点提醒❌ 不分场景乱设:IO密集任务设成“CPU核数”,导致线程不够用(比如4核设4个线程,IO等待时CPU空闲);
❌ 盲目加线程:以为线程越多越好,比如IO密集设100个线程,导致上下文切换频繁(每次切换耗时1-10μs,100个线程切换开销占CPU 20%以上);
❌ 忽略业务峰值:只按平时QPS设,比如平时100QPS设10个线程,峰值500QPS时任务堆积,要结合峰值预留1.5-2倍线程。
第三题:拒绝策略有哪几种?怎么选才合理?(猎上网/游族网络问过)当队列满+线程数达最大时,拒绝策略决定“怎么处理多余任务”。有4种拒绝策略,但很多人只会说“AbortPolicy抛异常”,不知道要按业务重要性选。
先理清:4种拒绝策略对比(表格+适用场景)拒绝策略
核心逻辑
适用场景
风险点
AbortPolicy
抛出RejectedExecutionException
核心业务(如支付、下单),需感知失败
没捕获异常会导致程序崩溃
DiscardPolicy
静默丢弃任务,不抛异常
非核心业务(如日志收集、统计)
任务丢了没监控,排查困难
DiscardOldestPolicy
丢弃队列最老的任务,加新任务
任务有“时效性”(如实时推荐)
可能丢重要老任务(如刚入队的订单)
CallerRunsPolicy
让“调用线程”(如main线程)自己执行任务
不想丢任务,且并发不高的场景
调用线程被阻塞,可能影响其他逻辑
关键补充:实际项目怎么选?核心业务(如订单创建):选AbortPolicy,但必须捕获异常,做重试(比如发MQ重试)或告警(钉钉/短信通知运维);非核心业务(如用户行为日志):选DiscardPolicy,但要加监控(比如用Prometheus统计丢弃数),避免丢太多没发现;实时性要求高的业务(如实时榜单):选DiscardOldestPolicy,但要确保“老任务不如新任务重要”(比如5分钟前的榜单数据可丢弃);低并发、不能丢任务的场景(如后台报表):选CallerRunsPolicy,哪怕调用线程阻塞,也比丢任务好。踩坑点提醒❌ 核心业务用DiscardPolicy:比如支付任务被丢了,没监控根本不知道,线上出大事;
❌ 用CallerRunsPolicy不控制并发:高并发场景下,调用线程(如Tomcat线程)全被阻塞,导致整个服务无响应;
❌ 忽略拒绝策略的监控:不管选哪种策略,都要统计“拒绝次数”,不然任务丢了、异常了都没法排查。
互动话题你项目里的线程池是怎么设参数的?有没有踩过“任务堆积”或“线程太多导致CPU飙升”的坑?评论区留个言,一起分析线程池配置是否合理~
#Java##线程##面试##CPU#
转载请注明来自海坡下载,本文标题:《tomcat优化面试题(线程池面试题核心参数拒绝策略)》
京公网安备11000000000001号
京ICP备11000001号
还没有评论,来说两句吧...