线程池超过核心线程后怎么创建

嗯... 线程池啊... 2 02 2 年的时候,我搞懂这个还挺费劲的。
核心线程数、工作队列、最大线程数... 这些参数,搞混了就完蛋。

当时我也懵,怎么突然就创建新线程了?后来才反应过来,得看具体情况。
比如,你在北京,公司系统提交任务,线程池里线程不够用,小于核心线程数,那好说,直接创建一个核心线程,去干。

但要是核心线程都满了呢?这就得看工作队列了。
比如,你用的是 LinkedBlockingQueue,这个队列挺大的,放得下。
任务就先搁这儿等着。
但要是队列也满了呢?比如,你用的是 SynchronousQueue,这个就特殊,它没容量,放不下就立马想办法。

立马干嘛?看看现在线程池里线程总数,是不是已经到最大线程数了?没到,那行,再创建一个非核心线程,去把这个任务干了。
这非核心线程,就是临时工,干完就... 不一定立马走,但核心线程肯定不走的。

要是队列满了,线程也到最大了?那只能按拒绝策略办了。
可能是 AbortPolicy,直接抛出异常,系统可能就崩溃了。
也可能是 DiscardPolicy,任务直接扔了,用户可能啥反应都没有。
这得看你怎么配置了。

参数的作用也挺关键的。
核心线程数,就是你得保证这个数目的线程一直干活的。
2 02 3 年在上海,我们系统搞个任务,要是核心线程数设得太低,任务积压得慌。
最大线程数,就是你最多能开多少个线程,不能无限开,不然 CPU 跑满,系统就卡死了。
工作队列,就是缓冲区,任务先放这儿,等有线程空了再拿。
队列类型不同,行为也不一样。
比如 LinkedBlockingQueue,放得下,慢慢来。
SynchronousQueue,放不下就立马创建新线程,不管用不用。

场景例子也挺多的。
固定线程池,比如你用 Executors.newFixedThreadPool(1 0),那核心线程数等于最大线程数,等于 1 0队列是 LinkedBlockingQueue,这个队列是有界的,但通常挺大的。
所以核心线程饱和了,任务就放队列里等,不会创建新线程。
这适合任务量稳定的情况。

缓存线程池,Executors.newCachedThreadPool(),这个核心线程数是 0,最大线程数是 Integer.MAX_VALUE,非常大。
队列是 SynchronousQueue,没容量。
所以每次提交任务,都会尝试创建新线程。
闲着超过 6 0 秒,这个临时线程就没了。
这适合任务量波动大的情况。

单线程池,Executors.newSingleThreadExecutor(),核心线程数 1 ,最大线程数也是 1 队列也是 LinkedBlockingQueue。
任务一个一个来,排队执行,不会创建新线程。
这适合必须顺序执行任务的场景。

反正... 线程池搞起来,参数得看清楚。
2 02 2 年的时候,我还在 debug 这个,有时候线程数突然增到 1 00,CPU 就炸了。
可能我偏激,觉得线程太多不好控制。
但不管怎么说,这几个参数,核心、队列、最大,得配合着用才行。

美团面试官:高并发、任务执行时间短的业务怎样使用线程池?

那天在咖啡厅,我看着手边的咖啡杯,突然想到一个问题:如果咖啡厅的服务员每接一个订单,都需要重新创建一个线程来处理,那会是怎样的场景?想想看,咖啡厅里人很多,每个客人点一杯咖啡,服务员就要开一个新线程,那咖啡厅的电脑得有多强大啊。
但是,这样真的高效吗?显然不是。
于是,我意识到,在高并发场景下,频繁创建和销毁线程是种资源浪费,得想个办法来优化。

等等,还有个事,我之前在一家互联网公司实习的时候,他们就是用线程池来处理高并发任务的。
他们设置的核心线程数比CPU核心数多一个,最大线程数比核心线程数多两个,任务队列用的是LinkedBlockingQueue,线程工厂是自定义的,拒绝策略选择了CallerRunsPolicy。
这样一来,他们既避免了频繁创建销毁线程,又减少了锁竞争,提升了吞吐量。
我记得当时他们的系统在高并发下表现还不错,处理速度快,用户体验也很好。

所以,针对高并发、任务执行时间短的业务场景,使用线程池确实是个不错的选择。
但是,具体如何配置线程池,如何优化任务调度策略,如何控制资源竞争,这些都是需要仔细考虑的问题。
就像咖啡厅的服务员,如果他们能合理安排自己的工作,既不会手忙脚乱,又能高效服务每一位客人,那该多好。
不过,这背后得有多少细节和技巧啊。

别再背线程池的七大参数了,现在面试官都这么问

说实话,讲线程池这东西,光背参数肯定不行。
我当年面试的时候,就栽在实现细节上。
今天咱们就掰开了揉碎了聊聊,核心线程为啥不超时销毁,状态机又是怎么转的。

先说核心线程保活。
你瞅瞅源码,getTask()方法里那判断条件workerCount<=corePoolSize,这才是关键。
核心线程拿的是workQueue.take(),这玩意儿无限阻塞,靠的是LockSupport.park()实现零CPU占用的等待。
有意思的是,这种设计避免了频繁创建线程的开销——你想想,要是每个任务都新建线程,系统得累成啥样?但状态机一变(比如SHUTDOWN),就会通过interruptIdleWorkers()把空闲线程给中断,唤醒它们处理剩余任务。

非核心线程那边就完全不同了。
它们用的是workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),超时就返回null,导致runWorker()循环终止,线程就被回收。
这里有个特别重要的保护机制:任务执行中保护。
runWorker()里通过w.lock()加锁,确保任务执行期间不会被中断。
中断时机也很讲究,只有在空闲时(getTask()返回null)才触发,保证正在执行的任务不受影响。

Worker生命周期这事儿,说起来头绪不少。
addWorker()里那些状态检查,线程池未关闭且线程数未超限(核心/非核心要分开看),这些细节必须抠清楚。
原子操作通过CAS保证线程数增减的线程安全,这一点面试官肯定要问。
然后创建Worker对象封装Runnable任务和线程,启动线程执行runWorker()。
runWorker()里那个循环取任务的逻辑,从队列获取任务,直到队列为空且线程数超限,异常处理用try-finally保证Worker异常退出时调用processWorkerExit()回收资源。

任务获取的阻塞策略特别有意思。
核心线程无限阻塞(take()),非核心线程超时阻塞(poll(keepAliveTime))。
线程池状态变更时,通过interruptIdleWorkers()中断空闲线程,这个机制必须说清楚。

状态机设计这块,高3 位表示线程池状态(RUNNING/SHUTDOWN/STOP),低2 9 位表示工作线程数。
原子操作通过位运算实现状态与线程数的快速解析。
状态转换规则也很关键,RUNNING->SHUTDOWN->STOP->TIDYING->TERMINATED,这个有序性决定了行为。
比如SHUTDOWN会拒绝新任务,但处理队列剩余任务;STOP会中断所有线程,连队列任务都丢弃。

面试高频题里,Q1 核心线程为啥不超时销毁?这得从源码讲起:核心线程在getTask()中调用workQueue.take()无限阻塞,依赖队列的LockSupport.park()实现零CPU占用等待。
这种设计避免了频繁创建/销毁线程的开销,同时通过状态机中断机制保证线程池关闭时的资源清理。
Q2 如何保证关闭线程池时不丢失任务?这得讲清楚shutdown()和shutdownNow()的区别:前者转为SHUTDOWN,中断空闲线程,继续执行队列任务;后者转为STOP,立即中断所有线程,返回未处理任务列表。
关键区别就在于状态机转换逻辑与中断策略的选择。
Q3 动态调整corePoolSize的陷阱?setCorePoolSize()会触发中断空闲线程(interruptIdleWorkers())和补充创建新核心线程(addWorker(null,true))这些操作。
这提醒我们参数修改不能简单赋值,要考虑任务队列状态。

线程池设计哲学这东西,空间换时间(任务队列缓存请求),惰性创建(线程按需创建),优雅降级(拒绝策略防止系统过载),状态驱动(所有行为围绕状态机展开)。
面试时要是能结合源码分析,比如addWorker()/getTask()这些方法的逻辑,讲清楚设计思想,比机械记忆参数强多了。

说白了,理解Worker生命周期、状态机设计与任务调度机制,能将参数转化为有机整体。
面试时展现对并发编程本质的理解(如锁优化、阻塞队列协作),这种东西比死记硬背参数要有用得多。