一、工厂招工难题:为什么需要线程池
在 Java 的 "编程工厂" 里,每个任务都是需要工人(线程)完成的订单。如果每次来订单就招新工人,用完就解雇,会导致:
-
招聘解雇成本高(线程创建销毁消耗资源)
-
新工人培训慢(线程初始化耗时)
-
工人数量失控(线程过多导致 OOM)
线程池就像工厂的固定工人团队,通过重复利用工人解决这些问题:
- 重用现有工人,降低招聘成本(线程复用)
- 工人随时待命,订单来了立即开工(响应速度快)
- 统一管理工人数量,避免混乱(线程数可控)
二、四种工厂团队:不同场景的线程池
1. 临时工团队:newCachedThreadPool
这是一个灵活的临时工团队:
-
没有固定人数,按需招聘
-
工人空闲 60 秒就会被解雇
-
订单太多时无限扩招(理论上限)
java
ini
// 场景:突发性订单处理
ExecutorService tempWorkers = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
final int orderId = i;
tempWorkers.execute(() -> {
System.out.println("临时工" + Thread.currentThread().getName() + "处理订单" + orderId);
try {
Thread.sleep(500); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
});
try {
Thread.sleep(1000); // 模拟订单间隔
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出结果:
plaintext
arduino
临时工pool-1-thread-1处理订单0
临时工pool-1-thread-1处理订单1
临时工pool-1-thread-1处理订单2
临时工pool-1-thread-1处理订单3
临时工pool-1-thread-1处理订单4
核心特点:同一个工人处理所有订单,空闲 60 秒后解雇,适合突发性、短时间任务。
2. 固定流水线:newFixedThreadPool
这是固定人数的流水线团队:
-
固定 3 名工人
-
订单多了排队等待
-
工人永远不解雇
java
ini
// 场景:稳定流量的生产线
ExecutorService fixedWorkers = Executors.newFixedThreadPool(3);
for (int i = 0; i < 6; i++) {
final int orderId = i;
fixedWorkers.execute(() -> {
System.out.println("固定工人" + Thread.currentThread().getName() + "处理订单" + orderId);
try {
Thread.sleep(1000); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
输出结果:
plaintext
arduino
固定工人pool-1-thread-1处理订单0
固定工人pool-1-thread-2处理订单1
固定工人pool-1-thread-3处理订单2
固定工人pool-1-thread-1处理订单3
固定工人pool-1-thread-2处理订单4
固定工人pool-1-thread-3处理订单5
核心特点:3 名工人轮流工作,订单排队等待,适合流量稳定的任务。
3. 单人工作室:newSingleThreadExecutor
这是只有一个工人的工作室:
-
所有订单按顺序处理
-
工人永远不解雇
-
保证订单处理顺序
java
ini
// 场景:需要顺序执行的任务
ExecutorService singleWorker = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3; i++) {
final int orderId = i;
singleWorker.execute(() -> {
System.out.println("唯一工人" + Thread.currentThread().getName() + "处理订单" + orderId);
try {
Thread.sleep(1000); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
输出结果:
plaintext
arduino
唯一工人pool-1-thread-1处理订单0
唯一工人pool-1-thread-1处理订单1
唯一工人pool-1-thread-1处理订单2
核心特点:单线程顺序执行,适合需要保证顺序的任务。
4. 定时巡检队:newScheduledThreadPool
这是负责定时任务的巡检团队:
-
3 名固定工人
-
可以安排定时订单或周期巡检
-
订单按计划执行
java
less
// 场景:定时任务和周期任务
ScheduledExecutorService scheduledWorkers = Executors.newScheduledThreadPool(3);
// 1秒后执行一次任务
scheduledWorkers.schedule(() -> {
System.out.println("定时工人" + Thread.currentThread().getName() + "执行1秒后任务");
}, 1, TimeUnit.SECONDS);
// 2秒后开始,每3秒执行一次
scheduledWorkers.scheduleAtFixedRate(() -> {
System.out.println("定时工人" + Thread.currentThread().getName() + "执行周期任务");
}, 2, 3, TimeUnit.SECONDS);
输出结果:
plaintext
arduino
定时工人pool-1-thread-1执行1秒后任务
定时工人pool-1-thread-1执行周期任务
定时工人pool-1-thread-2执行周期任务
定时工人pool-1-thread-2执行周期任务
...
核心特点:支持定时和周期任务,适合日志统计、定时备份等场景。
三、工厂管理核心:ThreadPoolExecutor 原理
1. 工厂配置参数
线程池的核心是ThreadPoolExecutor
,就像工厂的人员配置表:
java
java
// 工厂配置示例
ThreadPoolExecutor factory = new ThreadPoolExecutor(
2, // 核心工人数量(corePoolSize):至少保留2名固定工人
5, // 最大工人数量(maximumPoolSize):最多招5名工人
60, // 空闲时间(keepAliveTime):工人空闲60秒后可解雇
TimeUnit.SECONDS, // 时间单位(unit)
new ArrayBlockingQueue<>(10), // 订单队列(workQueue):最多存10个订单
Executors.defaultThreadFactory(), // 招聘工厂(threadFactory)
new ThreadPoolExecutor.AbortPolicy() // 订单太多时的策略(handler)
);
2. 订单处理流程
当新订单到来时,工厂按以下流程处理:
-
检查固定工人是否足够:如果现有工人少于 2 名,立即招新工人处理订单
-
订单入队:如果固定工人已满,订单放入队列等待
-
扩招临时工:如果队列已满,招临时工(最多到 5 名)处理订单
-
拒绝订单:如果临时工也满了,按策略处理(默认拒绝)
java
scss
// 订单处理流程图解
void processOrder(Runnable task) {
int workerCount = getWorkerCount();
if (workerCount < corePoolSize) {
hireNewWorker(task); // 招新固定工人
} else if (workQueue.hasSpace()) {
workQueue.add(task); // 订单入队
} else if (workerCount < maximumPoolSize) {
hireTemporaryWorker(task); // 招临时工
} else {
rejectTask(task); // 拒绝订单
}
}
3. 订单队列类型
不同的订单队列就像不同的仓库:
- SynchronousQueue(临时工团队用):没有仓库,订单直接给工人,没工人就招新
- LinkedBlockingQueue(固定团队用):无限仓库,订单无限排队
- ArrayBlockingQueue(自定义工厂用):有限仓库,订单超过数量就招临时工
4. 订单太多怎么办:饱和策略
当工厂满负荷时,有四种处理策略:
- AbortPolicy(默认):直接拒绝订单,抛出异常
- CallerRunsPolicy:让老板(调用者线程)自己处理订单
- DiscardPolicy:丢弃最新订单
- DiscardOldestPolicy:丢弃最老的订单,处理新订单
四、工厂优化:如何配置高效线程池
1. 按任务类型配置团队规模
CPU 密集型任务(计算型)
- 场景:数学计算、加密解密
- 配置:工人数量 = CPU 核心数 + 1
- 原因:工人一直忙碌,多一个备用工人防止偶尔的上下文切换
IO 密集型任务(等待型)
- 场景:网络请求、文件读写
- 配置:工人数量 = CPU 核心数 × 2
- 原因:工人经常等待 IO,需要更多工人利用 CPU
混合型任务(计算 + 等待)
- 场景:既有计算又有网络请求
- 配置:拆分为 CPU 和 IO 任务,分别使用不同线程池
2. 工厂监控:掌握运行状态
通过线程池提供的参数监控工厂运行:
java
csharp
// 监控工厂状态
void monitorFactory(ThreadPoolExecutor factory) {
System.out.println("总订单数: " + factory.getTaskCount());
System.out.println("已完成订单: " + factory.getCompletedTaskCount());
System.out.println("最大工人数: " + factory.getLargestPoolSize());
System.out.println("当前工人数: " + factory.getPoolSize());
System.out.println("活跃工人数: " + factory.getActiveCount());
}
3. 自定义工厂:扩展监控功能
继承线程池添加监控功能:
java
java
// 自定义监控线程池
class MonitoredThreadPool extends ThreadPoolExecutor {
private long startTime = System.currentTimeMillis();
private long totalTaskTime = 0;
private int taskCount = 0;
public MonitoredThreadPool(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("开始执行任务: " + r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
long endTime = System.currentTimeMillis();
totalTaskTime += (endTime - startTime);
taskCount++;
System.out.println("完成任务: " + r + ", 耗时: " + (endTime - startTime) + "ms");
}
@Override
protected void terminated() {
System.out.println("工厂关闭,共处理" + taskCount + "个任务,总耗时" + totalTaskTime + "ms");
}
}
五、工厂管理总结:线程池最佳实践
-
优先使用工厂方法:
- 临时工团队:
Executors.newCachedThreadPool()
- 固定团队:
Executors.newFixedThreadPool(n)
- 单人工厂:
Executors.newSingleThreadExecutor()
- 定时团队:
Executors.newScheduledThreadPool(n)
- 临时工团队:
-
避免 OOM 陷阱:
- 对无限队列(如 LinkedBlockingQueue)要谨慎
- 高并发场景使用有界队列 + 合理最大线程数
-
处理异常任务:
- 为线程池设置自定义异常处理
- 避免因单个任务异常导致整个线程池崩溃
-
优雅关闭工厂:
java
scss// 有序关闭工厂 factory.shutdown(); // 不再接收新订单,处理完现有订单后关闭 try { if (!factory.awaitTermination(60, TimeUnit.SECONDS)) { factory.shutdownNow(); // 强制关闭 } } catch (InterruptedException e) { factory.shutdownNow(); Thread.currentThread().interrupt(); }
通过合理使用线程池,就像工厂高效管理工人一样,Java 程序可以更高效地利用系统资源,减少开销,提高响应速度,避免资源耗尽问题。不同的任务类型选择不同的线程池配置,加上适当的监控和优化,就能构建出稳定高效的多线程应用。