深入浅出Java线程池
什么是线程池?
想象你开了一家快递站,每天有很多包裹要派送。如果每来一个包裹就雇一个新快递员,送完就解雇,这样效率很低,因为:
- 频繁招聘和解雇成本高(对应线程创建和销毁开销大)
- 新快递员不熟悉路线(线程需要时间初始化)
- 快递员太多时管理混乱(系统资源耗尽)
线程池就像你预先雇佣的一批固定快递员(线程),有包裹(任务)来了就分配给他们,送完继续等待新任务,这样效率更高。
为什么需要线程池?
- 降低资源消耗:重复利用已创建的线程,避免频繁创建销毁
- 提高响应速度:任务到达时直接使用现有线程,无需等待线程创建
- 便于管理:可以统一分配、监控和调优线程资源
Java线程池核心类
Java中的线程池主要通过java.util.concurrent
包中的ExecutorService
接口及其实现类ThreadPoolExecutor
来实现。
线程池工作原理
线程池就像一个有管理的"线程工厂+任务队列":
- 核心线程:池中常驻的基本劳动力,即使空闲也不销毁
- 任务队列:当核心线程都忙时,新任务进入队列等待
- 非核心线程:当队列满了,创建额外线程帮忙(有数量限制)
- 拒绝策略:当线程和队列都满了,如何处理新任务
创建线程池的常用方法
Java提供了Executors
工厂类来创建常见类型的线程池:
java
// 1. 固定大小线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// 2. 单线程池(保证任务顺序执行)
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
// 3. 可缓存线程池(自动扩容缩容)
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 4. 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
更灵活的ThreadPoolExecutor
实际上,上述工厂方法内部都是使用ThreadPoolExecutor
构造的。直接使用它可更精细控制:
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 空闲线程存活时间(秒)
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
线程池重要参数
- corePoolSize:核心线程数,池中常驻线程数量
- maximumPoolSize:最大线程数,池中允许的最大线程数
- keepAliveTime:非核心线程空闲时的存活时间
- workQueue:任务队列,保存等待执行的任务
- threadFactory:创建线程的工厂
- handler:拒绝策略,当线程和队列都满时的处理方式
四种拒绝策略
- AbortPolicy(默认):直接抛出RejectedExecutionException异常
- CallerRunsPolicy:让提交任务的线程自己执行该任务
- DiscardPolicy:默默丢弃无法处理的任务
- DiscardOldestPolicy:丢弃队列中最老的任务,然后重试提交
使用示例
java
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
// 提交10个任务
for (int i = 1; i <= 10; i++) {
final int taskId = i;
pool.execute(() -> {
System.out.println("任务" + taskId + "正在执行,线程:" + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务" + taskId + "执行完毕");
});
}
// 关闭线程池
pool.shutdown();
}
}
线程池生命周期
- RUNNING:接受新任务,处理队列任务
- SHUTDOWN:不接受新任务,但处理队列中的任务
- STOP:不接受新任务,不处理队列任务,中断正在执行的任务
- TIDYING:所有任务终止,workerCount为0
- TERMINATED:terminated()方法执行完毕
最佳实践
-
根据任务类型选择合适线程池:
- CPU密集型:线程数 ≈ CPU核心数
- IO密集型:线程数可以多一些(如CPU核心数×2)
-
避免使用无界队列,可能导致内存溢出
-
合理设置线程存活时间
-
为线程池中的线程设置有意义的名字,便于排查问题
-
考虑使用自定义的ThreadFactory,设置线程优先级、异常处理等
线程池是Java并发编程的重要工具,合理使用可以显著提高程序性能,但需要根据实际场景仔细配置参数。