线程池的基本概述
线程池是一种对线程进行复用和管理的机制。在 Java 中,通过线程池来控制线程的数量和线程的状态,提高线程的执行效率,避免频繁创建和销毁线程带来的资源消耗。就像餐厅的传菜员。餐厅有很多顾客下的订单(任务),而传菜员(线程)负责将菜送到顾客那里。如果每个订单都单独找一个传菜员,会导致传菜员数量过多,餐厅资源紧张。所以餐厅会安排固定的传菜员团队(线程池),他们负责不断地在厨房和顾客之间来回送菜(执行任务),提高服务效率。

线程池的状态 
状态转换如图所示: 
线程池的简单使用流程
简单的使用流程可以总结为:任务提交 → 核心线程执行 → 任务队列缓存 → 非核心线程执行 → 拒绝策略处理 第一步,创建线程池。
第二步,调用线程池的 execute()方法,准备执行任务。
- 如果正在运行的线程数量小于 corePoolSize,那么线程池会创建一个新的线程来执行这个任务;
- 如果正在运行的线程数量大于或等于 corePoolSize,那么线程池会将这个任务放入等待队列;
- 如果等待队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么线程池会创建新的线程来执行这个任务;
- 如果等待队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会执行拒绝策略。
第三步,线程执行完毕后,线程并不会立即销毁,而是继续保持在池中等待下一个任务。
第四步,当线程空闲时间超出指定时间,且当前线程数量大于核心线程数时,线程会被回收。
执行流程图如下所示:
简单代码示例
class ThreadPoolDemo {
public static void main(String[] args) {
// 创建一个线程池
ExecutorService threadPool = new ThreadPoolExecutor(
3, // 核心线程数
6, // 最大线程数
0, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10), // 等待队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 模拟 10 个线程办理业务
try {
for (int i = 1; i <= 10; i++) {
final int tempInt = i;
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "办理业务" + tempInt);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}线程池的七个参数
ThreadPoolExecutor的构造方法如下所示:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)什么?白雪公主和他的七个小矮人?有点意思哈。下面讲讲每个参数的含义:
corePoolSize 核心线程数目
含义:线程池中始终保留的最小线程数,即使这些线程处于空闲状态也不会被回收(除非调用
allowCoreThreadTimeOut(true)) 作用:保证在并发请求数少于等于该值时,所有任务都能立即得到线程执行,无需排队maximumPoolSize 最大线程数目 核心线程数+救急线程数 含义:线程池允许的最大线程数 作用:当活动线程数达到
corePoolSize且任务队列已满时,会继续创建新线程直至达到maximumPoolSize;超过该阈值的新任务则 按拒绝策略处理keepAliveTime 救急线程的存活时间 含义:当线程数量超过
核心线程数目时,多余的线程在空闲超过此时长后会被回收 作用:控制线程池在负载低时的资源释放;unit与之配合指定时间单位unit 时间单位,针对救急线程--------控制救急线程生存时间对的单位 含义:
keepAliveTime参数的时间单位,取值为TimeUnit枚举 常用:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECOND(毫秒)workQueue 阻塞队列 含义:用于缓存提交但尚未执行的任务的阻塞队列,类型为
BlockingQueue<Runnable>作用:当线程数达到corePoolSize后,新任务会进入该队列等待空闲线程threadFactory 线程工厂----可以为线程创建时起个好听的名字? 含义:用于创建新线程的工厂接口,默认实现可通过
Executors.defaultThreadFactory()获取 作用:可定制线程名、是否守护线程、优先级等handler 拒绝策略 含义:当线程池饱和(即线程数达
maximumPoolSize且队列已满)时,对新提交任务的处理策略 常见策略:AbortPolicy:抛出RejectedExecutionException(默认)CallerRunsPolicy:由调用线程执行该任务DiscardPolicy:直接丢弃任务DiscardOldestPolicy:丢弃队列中最旧的一个任务,再尝试提交当前任务
一些代码示例
// 1. 固定大小线程池
ThreadPoolExecutor fixedPool = new ThreadPoolExecutor(
5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
// 2. 缓存线程池
ThreadPoolExecutor cachedPool = new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 3. 自定义线程工厂和拒绝策略
ThreadFactory tf = runnable -> {
Thread t = new Thread(runnable);
t.setName("worker-" + t.getId());
return t;
};
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
ThreadPoolExecutor customPool = new ThreadPoolExecutor(
2, 10,
30L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
tf, handler
);
每个参数之间关系就是:任务优先使用核心线程执行,满了进入等待队列,队列满了启用非核心线程备用,线程池达到最大线程数量后触发拒绝策略,非核心线程的空闲时间超过存活时间就被回收
线程池的阻塞队列
ArrayBlockingQueue- 特性:基于数组、有界(需在构造时指定容量)、采用 FIFO 原则
- 使用场景:希望严格控制队列大小,避免内存占用过高
LinkedBlockingQueue- 特性:基于链表,可选有界或无界(默认容量为
Integer.MAX_VALUE),FIFO。 - 使用场景:任务生产、消费速度不确定时,可使用无界队列以防过早拒绝。
- 特性:基于链表,可选有界或无界(默认容量为
PriorityBlockingQueue- 特性:基于堆,无界,按优先级(
Comparator)排序,非 FIFO - 使用场景:需要优先执行高优先级任务时
- 特性:基于堆,无界,按优先级(
SynchronousQueue- 特性:零容量队列,生产者线程插入必须等待消费者线程取走才能继续。
- 使用场景:
newCachedThreadPool()使用,适合执行大量短生命周期任务,不保留队列
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(50);
BlockingQueue<Runnable> lq = new LinkedBlockingQueue<>();
BlockingQueue<Runnable> pq = new PriorityBlockingQueue<>();
BlockingQueue<Runnable> sq = new SynchronousQueue<>();Executors提供的一些工厂方法:
- newFixedThreadPool

- newCachedThreadPool

- newSingleThreadExecutor

线程池提交任务----submit与execute
execute 方法没有返回值,适用于不关心结果和异常的简单任务
threadsPool.execute(new Runnable() {
@Override public void run() {
System.out.println("execute() 方法提交的任务");
}
});submit 有返回值,适用于需要获取结果或处理异常的场景
Future<Object> future = executor.submit(harReturnValuetask);
try { Object s = future.get(); }
catch (InterruptedException e | ExecutionException e) {
// 处理无法执行任务异常
} finally {
// 关闭线程池 executor.shutdown();
}线程池的关闭
可以调用线程池的shutdown或shutdownNow方法来关闭线程池
调用shutdown方法,线程池的状态变为SHUTDOWN ①不会再接收新的任务 ②但是已经提交了的任务会执行完 ③shutdown方法不会阻塞调用线程的执行
调用shutdownNow方法,线程池的状态变为STOP ①不会接收新任务 ②会将队列中的任务返回 ③并用 interrupt 的方式中断正在执行的任务 注意shutdownNow不会真正终止正在运行的任务,只是给任务线程发送 interrupt 信号,任务是否能真正终止取决于线程是否响应 InterruptedException
线程池的异常处理
1、try-catch直接捕获处理异常
executor.execute(() -> {
try {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
} catch (Exception e) {
System.err.println("捕获异常:" + e.getMessage());
}
});2、使用 Future 获取异常
Future<Object> future = executor.submit(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
return result;
});
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
System.err.println("捕获异常:" + e.getMessage());
}3、自定义 ThreadPoolExecutor重写 afterExecute方法
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t != null) {
System.err.println("捕获异常:" + t.getMessage());
}
}
};
executor.execute(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
});4、使用 UncaughtExceptionHandler·捕获异常
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.setThreadFactory(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.println("捕获异常:" + e.getMessage());
}
});
return thread;
}
});
executor.execute(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
});如果不关心任务返回值,建议使用 UncaughtExceptionHandler 如果关心任务返回值,建议使用 Future 如果想要全局捕获所有任务异常,建议重写 afterExecute 方法
来点面试题
解释 ThreadPoolExecutor 的七个参数及其作用。
当核心线程数已满且队列已满时,新任务如何处理?
LinkedBlockingQueue 与 SynchronousQueue 的区别及适用场景。
如何自定义线程工厂与拒绝策略?请给出代码示例。
为什么要使用线程池而不是直接创建新线程?
如何监控线程池运行状态(如活跃线程数、队列大小、已完成任务数)?
allowCoreThreadTimeOut(true) 有何作用?
请简述实现一个简单线程池的思路。
在高并发场景下,如何调优 corePoolSize、maximumPoolSize 和队列容量?线程池如何调优
Executors.newFixedThreadPool() 与 new ThreadPoolExecutor() 在底层有何区别?
