线程池本质是“任务调度+资源节制”机制,通过有限线程、有界/无界队列和明确拒绝策略应对并发流量,不解决并发安全而防资源滥用;核心线程数指保底不回收线程数;任务优先分配空闲核心线程而非直接入队;LinkedBlockingQueue默认无界易致内存溢出;并发控制需选对同步原语,volatile不保证原子性;CountDownLatch用于等待完成,CyclicBarrier用于协同到达;Semaphore不绑定线程,ReentrantLock必须同线程释放;拒绝策略是熔断开关,非兜底方案;自定义策略须轻量;线程池与并发控制须配合,否则单点失效全局崩塌;IO密集型宜大线程池,CPU密集型建议设为CPU核数;并发问题根源在于未厘清变量读写时机。

线程池不是“池子”,而是「任务调度+资源节制」的组合机制
很多人一上来就背 corePoolSize、maximumPoolSize,却忽略线程池本质是**用有限线程 + 有界/无界队列 + 明确拒绝策略**来对抗不可控的并发流量。它不解决并发安全问题,只解决资源滥用问题。
- 核心线程数(
corePoolSize)不是“最小线程数”,而是“保底不回收的线程数”——哪怕空闲 60 秒,只要没超keepAliveTime,它也不会被销毁 - 任务进队列前,线程池**先看有没有空闲核心线程**,而不是先塞队列;这点和直觉相反,但决定了高并发下是否立刻扩容
-
LinkedBlockingQueue默认是无界队列(容量Integer.MAX_VALUE),一旦用错,newFixedThreadPool就会内存溢出——因为所有任务全堆在队列里,线程来不及消费
并发控制 ≠ 加锁,而是「选对同步原语」+「明确临界区边界」
加 synchronized 或 ReentrantLock 是最常见动作,但真正踩坑的是:不知道什么时候该用 CountDownLatch,什么时候该用 Semaphore,甚至把 volatile 当锁用。
-
volatile只保证可见性,不保证原子性——counter++即使加了volatile依然线程不安全 -
CountDownLatch是“等别人做完我再走”,适合主流程等待多个异步任务完成;CyclicBarrier是“大家一起到齐才开工”,适合多线程协作分阶段计算 -
Semaphore(1)看似等于锁,但它不绑定线程——A 线程 acquire(),B 线程 release() 合法;而ReentrantLock必须同一线程 unlock()
ThreadPoolExecutor 的拒绝策略不是兜底,而是「系统熔断开关」
默认的 AbortPolicy 抛 RejectedExecutionException,看似简单,但在生产环境常导致上游重试风暴。它真正的意义是:告诉调用方“此刻已不可服务”,而非“换个方式再试”。
-
CallerRunsPolicy表面温和(由提交线程自己执行),但可能阻塞业务线程,尤其在 Web 容器中会让请求线程卡住,引发连接超时 -
DiscardOldestPolicy丢的是队列头任务,如果队列里全是延迟敏感任务(如定时通知),反而丢掉最该执行的 - 自定义拒绝策略必须轻量——不能记录日志、不能远程调用,否则拒绝逻辑本身就成了新瓶颈
线程池与并发控制必须配合使用,否则单点失效即全局崩塌
比如你用 ConcurrentHashMap 存共享状态,却把更新逻辑扔进线程池执行,却不加任何同步——那只是把数据竞争从“同一时刻多个线程改 map”变成“同一时刻多个线程改 map + 多个线程读 map”,问题一点没少。
立即学习“Java免费学习笔记(深入)”;
- 线程池负责“谁来跑”,并发控制负责“怎么跑不打架”——二者缺一不可
- 常见反模式:
submit(() -> { cache.put(key, value); }),看似用了线程池,但cache若是普通HashMap,照样并发写崩 - IO 密集型任务(如 HTTP 调用)适合大线程池 + 长队列;CPU 密集型任务(如加解密)线程数建议设为
Runtime.getRuntime().availableProcessors(),再多就是上下文切换开销










