一、线程池是什么?(通俗解释)

我们平时代码中 new Thread() 是临时创建线程、用完就扔。

线程池就是一个“线程复用池子”:提前创建好一批线程,统一管理,任务来了直接用,任务做完线程不销毁、继续等待下一个任务。

Java线程池底层核心实现类:ThreadPoolExecutor,由7大核心参数控制所有运行规则。

线程池七大核心参数(源码+通俗详解)

java

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

1. corePoolSize 核心线程数

线程池里常驻的正式员工,默认空闲也不销毁,一直待命处理任务。

2. maximumPoolSize 最大线程数

线程池最多能容纳的总线程数。

总线程 = 核心线程(正式工) + 非核心线程(临时工)

3. keepAliveTime 空闲存活时间

专门给临时非核心线程用的:临时工空闲超过这个时间,直接辞退回收,节省资源。

4. unit 时间单位

配合空闲时间使用:毫秒、秒、分钟。

5. workQueue 阻塞队列

任务排队缓冲区。核心线程忙完了,新任务就先排队进队列。

6. threadFactory 线程工厂

专门用来创建线程,可以自定义线程名称、优先级,方便线上排查BUG。

7. handler 拒绝策略

池子满了、队列也满了,新任务没地方放,触发的兜底处理规则。

记忆口诀:核心最大空闲时,单位队列工厂拒


二、为什么一定要用线程池?(不用的后果)

如果频繁 new Thread() 会出现的问题

1. 非常耗资源:线程创建、销毁是重量级操作,频繁创建会大量消耗CPU、内存,产生大量上下文切换开销。

2. 响应速度慢:每次任务都要新建线程,浪费创建时间。

3. 并发完全不可控:任务多就无限创建线程,极易OOM、CPU打满、服务直接宕机。

4. 无法管理:线程散乱,无法监控、无法统一关闭、出问题无法排查。

使用线程池的四大好处

1. 线程复用:线程用完不销毁,循环干活,大幅节省资源。

2.响应更快:任务直接复用现成线程,不用重新创建。

3. 并发可控:限制最大线程数,保护服务器不会被打崩。

4. 可监控、可扩展:支持排队、限流、拒绝策略、任务监控。


三、线程池完整执行流程(小白必懂、面试必考)

每当我们提交一个新任务,线程池严格按照 4步顺序执行:

第一步:优先创建核心线程

当前工作线程数 < 核心线程数 → 直接新建核心线程执行任务。

第二步:核心线程满了,任务进队列

核心线程全部在忙 → 新任务进入阻塞队列排队。

第三步:队列满了,创建临时线程

队列存不下了 → 创建非核心临时线程处理任务(总数不超过最大线程数)。

第四步:全部满载,触发拒绝策略

核心线程忙、队列满、临时线程也开满了 → 执行拒绝策略。

流程口诀:先开核心,再进队列;队列满了开临时,满载触发拒绝器

线程池四大内置拒绝策略

1. AbortPolicy(默认):直接抛异常,任务失败。

2. DiscardPolicy:默默丢掉新任务,不报错、无日志。

3. DiscardOldestPolicy:丢掉队列里最久没执行的任务,让当前新任务优先执行。

4. CallerRunsPolicy:谁提交的任务,谁自己跑(主线程执行,起到限流作用)。

拒绝策略口诀:默认抛异常,丢弃新任务,丢弃最老的,调用者来跑


四、JDK自带四种预定义线程池(Executors)+ 致命弊端

JDK提供Executors工具类,可以快速创建线程池,但是生产一律禁止使用,全部有坑!

1. newFixedThreadPool(n) 固定线程池

特点:核心线程固定,不会创建临时线程;底层是无界队列

弊端:任务无限堆积,队列越来越大,直接 OOM内存溢出

2. newSingleThreadExecutor() 单线程池

特点:全程只有1个线程,任务串行执行。

弊端:同样无界队列,任务堆积严重,极易OOM。

3. newCachedThreadPool() 缓存线程池

特点:无核心线程、最大线程数无限、使用SynchronousQueue不存任务。

弊端:来任务就新建线程,并发高会无限创建线程 → 线程爆炸、CPU飙升、OOM

4. newScheduledThreadPool() 定时线程池

特点:用来做延迟任务、周期性定时任务。

弊端:最大线程数无上限,高并发存在线程溢出风险。

阿里开发强制规范

禁止使用Executors创建线程池!

必须手动通过 ThreadPoolExecutor 构造方法自定义线程池:手动配置有界队列、合理最大线程数、自定义线程名、自定义拒绝策略,杜绝OOM风险。


五、线程池进阶核心知识点(小白易懂)

1. 核心线程回收机制

默认:核心线程永久存活,空闲也不回收。

如果开启 allowCoreThreadTimeOut(true):核心线程空闲超时也会被回收。

2. 四种阻塞队列作用与区别

ArrayBlockingQueue 有界队列:固定容量,生产推荐使用,可控不OOM。

LinkedBlockingQueue 无界队列:不限制任务数量,无限堆积OOM。

SynchronousQueue 同步队列:不存储任务,来任务必须立刻执行,适合瞬时高并发。

DelayQueue 延迟队列:任务延时执行,适合定时业务。

队列口诀:数组有界防溢出,链表无界易OOM;同步队列不缓存,延迟队列定时取

3. 线程池五大运行状态

RUNNING → SHUTDOWN → STOP → TIDYING → TERMINATED

RUNNING:正常运行,接收新任务。

SHUTDOWN:调用shutdown(),不接新任务,做完剩余任务再关闭。

STOP:调用shutdownNow(),不接新任务、中断正在执行的任务、丢弃排队任务。

TIDYING:所有任务结束,线程清空,准备关闭。

TERMINATED:线程池彻底关闭。

状态口诀:运行收任务,停机拒新活;停止断线程,整理终关闭

4. 线程池钩子扩展方法

线程池提供三个空方法,可自定义扩展:

beforeExecute():任务执行前触发

afterExecute():任务执行后触发

terminated():线程池关闭后触发

用途:统计耗时、记录日志、异常监控、资源释放。

5. 线程复用原理

线程执行完任务不会销毁,会循环从阻塞队列take()抢任务;

没有任务时线程阻塞休眠、释放CPU;

非核心线程超时没人用就回收,核心线程永久待命,实现线程复用。

6. execute 和 submit 提交任务区别

execute():只执行任务,异常直接抛出,当前线程销毁,线程池会新建线程补上。

submit():带返回值,异常会封装在Future中,调用get()才会报错,线程不会销毁。


六、线程池线程数配置公式 + 详细设计原理(重点)

1. CPU密集型任务(计算多、IO少)

场景:数据计算、加密、排序、大量运算。

配置公式:核心线程数 = CPU核心数 + 1

原理:充分占满CPU算力,防止线程频繁切换,最大化运算效率。

2. IO密集型任务(数据库、网络、文件请求)

场景:查数据库、调接口、读写文件、业务等待多。

配置经验值:CPU核心数 × 2 | 直接设置 20~50

设计原理(通俗解释)

IO密集型任务,线程大部分时间都在阻塞等待(等数据库返回、等网络响应),基本不占用CPU。

所以可以多开一些线程,让CPU在等待间隙处理更多任务,压榨空闲资源,大幅提升系统吞吐量。

3. 为什么不能设置无限大线程数?

1. 每一个线程都会占用独立栈内存,线程无限多会直接内存溢出OOM。

2. 操作系统切换线程需要上下文切换开销

线程过多 → CPU一直在切换线程、没时间处理业务 → 吞吐量暴跌、服务器卡死、服务宕机。


七、高频面试简答题(小白背诵版)

1. 核心线程会不会被回收?

默认永久存活,开启allowCoreThreadTimeOut(true)后会超时回收。

2. 为什么要自定义线程名称?

默认线程只有编号,线上CPU飙升、死锁、报错时,无法定位业务;自定义线程名可以快速排查问题。

3. 什么是线程饥饿?怎么解决?

同一线程池中,任务互相等待、全部阻塞,导致部分任务一直得不到执行。

解决:不同业务拆分独立线程池,做好线程池隔离。

4. 线程池怎么监控?

监控活跃线程数、队列堆积数量、已完成任务数,队列堆积过高及时告警、优化慢任务。

更多推荐