👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主
⛪️ 个人社区:个人社区
💞 个人主页:个人主页
🙉 专栏地址: ✅ Java 中级
🙉八股文专题:剑指大厂,手撕 Java 八股文
1. 线程池的执行流程和原理
线程池的执行流程:
- 提交任务:客户端通过
execute
或submit
方法将任务提交到线程池。 - 核心线程:如果当前运行的线程数小于核心线程数(
corePoolSize
),线程池会创建新的线程来执行任务,即使其他空闲的核心线程也可以执行新任务。 - 工作队列:如果当前运行的线程数已经达到核心线程数,任务会被放入工作队列(
workQueue
)中等待执行。 - 最大线程:如果工作队列已满且当前运行的线程数小于最大线程数(
maximumPoolSize
),线程池会创建新的线程来执行任务。 - 拒绝策略:如果当前运行的线程数已经达到最大线程数且工作队列已满,线程池会根据拒绝策略处理新提交的任务。
线程池的原理:
- 线程复用:线程池通过复用已创建的线程来执行新任务,减少了线程创建和销毁的开销。
- 资源控制:通过设置核心线程数、最大线程数和工作队列大小,可以有效控制资源的使用。
- 任务调度:线程池可以根据任务的优先级和类型进行调度,提高系统的响应速度和吞吐量。
2. 线程池的拒绝策略
线程池的拒绝策略决定了当线程池无法接受新任务时的行为。Java 提供了以下几种拒绝策略:
- AbortPolicy:默认策略,抛出
RejectedExecutionException
异常。 - CallerRunsPolicy:由调用线程(提交任务的线程)执行该任务,适用于任务不能丢弃的情况。
- DiscardPolicy:直接丢弃任务,不抛出异常。
- DiscardOldestPolicy:丢弃工作队列中最老的任务,然后尝试重新提交当前任务。
3. 如何确定线程池的核心线程数
确定线程池的核心线程数需要考虑以下几个因素:
- CPU 核心数:对于 CPU 密集型任务,核心线程数通常设置为
CPU 核心数 + 1
,以充分利用 CPU 资源。 - IO 密集型任务:对于 IO 密集型任务,核心线程数可以设置为
CPU 核心数 * 2
或更高,因为 IO 密集型任务大部分时间都在等待 IO 操作完成。 - 任务类型:根据任务的性质和系统负载情况,适当调整核心线程数。
- 系统资源:考虑系统内存和 CPU 资源的限制,避免过度占用资源导致系统性能下降。
4. 如何优雅的关闭线程池
优雅地关闭线程池意味着在关闭线程池之前,确保所有已提交的任务都已完成。Java 提供了以下方法来优雅地关闭线程池:
- shutdown:停止接收新任务,但允许已提交的任务继续执行,直到所有任务完成。
- awaitTermination:等待所有任务完成,超时后返回。
- shutdownNow:尝试停止所有正在执行的任务,并返回等待执行的任务列表。
5. 用 Java 实现优雅的关闭线程池
如何优雅地关闭线程池:
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executorService = new ThreadPoolExecutor(
4, // 核心线程数
10, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 工作队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 150; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " is completed by " + Thread.currentThread().getName());
});
}
// 优雅地关闭线程池
shutdownAndAwaitTermination(executorService);
}
public static void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // 不再接受新任务
try {
// 等待所有任务完成,最多等待 60 秒
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // 尝试停止所有正在执行的任务
// 再次等待所有任务完成,最多等待 10 秒
if (!pool.awaitTermination(10, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
pool.shutdownNow(); // (Re-)Cancel if current thread also interrupted
Thread.currentThread().interrupt(); // Preserve interrupt status
}
}
}
- 创建线程池:使用
ThreadPoolExecutor
创建一个线程池,指定核心线程数、最大线程数、空闲线程存活时间、工作队列和拒绝策略。 - 提交任务:通过
executorService.submit
方法提交多个任务。 - 优雅地关闭线程池:
- shutdown:停止接收新任务,但允许已提交的任务继续执行。
- awaitTermination:等待所有任务完成,超时后返回。
- shutdownNow:尝试停止所有正在执行的任务,并返回等待执行的任务列表。
通过这种方式,可以确保在关闭线程池之前,所有已提交的任务都能完成,从而实现优雅的关闭。
精彩专栏推荐订阅:在下方专栏👇🏻
✅ 2023年华为OD机试真题(A卷&B卷)+ 面试指导
✅ 精选100套 Java 项目案例
✅ 面试需要避开的坑(活动)
✅ 你找不到的核心代码
✅ 带你手撕 Spring
✅ Java 初阶