Java线程池详解
一、线程池是什么
以学校奶茶店招聘兼职为例:如果每次有订单都临时雇人送快递(创建线程),招聘成本(线程创建/销毁开销)将变得极高。线程池就像老板预先雇佣3个固定配送员(核心线程),高峰期临时增加人手(最大线程数),空闲时回收资源,有效解决了频繁创建/销毁线程的性能损耗问题。
二、标准库中的线程池
1. Executors工厂方法
Java通过java.util.concurrent.Executors
提供多种预置线程池:
// 固定线程数的线程池(核心=最大线程数)
ExecutorService fixedPool = Executors.newFixedThreadPool(10); // 弹性伸缩线程池(核心0,最大Integer.MAX_VALUE,空闲60秒回收)
ExecutorService cachedPool = Executors.newCachedThreadPool();// 单线程池(保证任务顺序执行)
ExecutorService singlePool = Executors.newSingleThreadExecutor();// 支持定时/周期任务的线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
一般使用第一个和第二个比较多
2. 终极构造器:ThreadPoolExecutor
所有预置线程池底层均通过ThreadPoolExecutor
实现,其完整构造方法如下:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler
)
构造方法参数解析
1. 核心线程控制
参数 | 说明 |
---|---|
corePoolSize | 核心线程数,即使空闲也不会销毁(除非allowCoreThreadTimeOut=true) |
maximumPoolSize | 线程池允许的最大线程数(含核心线程) |
示例:core=5, max=10
,当任务队列满时,线程数可扩容到10
2. 线程存活策略
参数 | 说明 |
---|---|
keepAliveTime | 非核心线程的空闲存活时间(默认60秒) |
unit | 时间单位(秒/毫秒等) |
3. 任务队列
BlockingQueue<Runnable> workQueue
的三种典型选择:
队列类型 | 行为特点 | 适用场景 |
---|---|---|
SynchronousQueue | 不存储任务,直接移交线程 | 高吞吐量(newCachedThreadPool) |
LinkedBlockingQueue | 无界队列(默认Integer.MAX_VALUE) | 固定线程数(newFixedThreadPool) |
ArrayBlockingQueue | 有界队列,需指定容量 | 流量削峰 |
4. 线程工厂与拒绝策略
参数 | 说明 |
---|---|
threadFactory | 自定义线程创建方式(可设置线程名、优先级等) |
RejectedExecutionHandler | 任务拒绝策略(当队列满且线程数达max时触发) |
四大内置拒绝策略:
AbortPolicy
(默认):抛出RejectedExecutionExceptionCallerRunsPolicy
:由提交任务的线程直接执行DiscardPolicy
:静默丢弃任务DiscardOldestPolicy
:丢弃队列最老任务并重试
参数配置实践建议
- CPU密集型任务:核心数 ≈ CPU核数(避免过多线程导致频繁上下文切换)
- IO密集型任务:核心数可适当增大(如2*CPU核数)
- 混合型任务:拆分线程池隔离处理(如计算任务与IO任务使用不同池)
- 队列选择:需要限流时使用有界队列,配合合理的拒绝策略
关于工厂模式,会有这个东西,其实是因为构造方法的局限性,比如两种构造方法参数列表相同导致无法重载,构造方法名称固定,无法通过方法名表达语义差异,对象创建逻辑与类本身耦合过紧
// 错误示例:参数类型相同导致编译错误
class Coordinate {
// 尝试用极坐标和直角坐标两种构造方式
public Coordinate(double r, double theta) {} // 极坐标
public Coordinate(double x, double y) {} // 直角坐标 ❌编译错误
}这时就会出现编译错误,解决方法,就是单独创建一个类,来实现不同的构造方法.工厂模式本质是通过将对象的实例化过程委托给专门的工厂类,解决了构造方法在语义表达和参数灵活性上的先天不足,是面向对象设计中"封装变化"原则的典型实践。
三、实现线程池
- 核心操作为 submit, 将任务加入线程池中
- 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
- 使用一个 BlockingQueue 组织所有的任务
- 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
- 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增 线程了.
/*** 工作线程类,负责从任务队列获取并执行任务* 继承Thread类实现多线程能力*/
class Worker extends Thread {// 线程共享的任务队列(注意泛型应为Runnable)private LinkedBlockingQueue<Runnable> queue;/*** 构造函数初始化任务队列* @param queue 线程池共享的任务阻塞队列*/public Worker(LinkedBlockingQueue<Runnable> queue) {super("worker");this.queue = queue;}@Overridepublic void run() {try {// 持续监听任务队列(建议将中断检查放在循环条件中)while (!Thread.interrupted()) {// 阻塞式获取任务,队列为空时线程等待Runnable task = queue.take();task.run(); // 执行任务}} catch (InterruptedException e) {// 被中断时退出线程(保持线程终止的响应能力)}}
}/*** 简易线程池实现类* 包含任务队列和工作线程管理*/
public class MyThreadPool {// 最大工作线程数(根据需求调整)private int maxWorkerCount = 10;// 线程安全的任务队列(使用阻塞队列保证线程安全)private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();/*** 提交任务到线程池* @param command 待执行的任务*/public void submit(Runnable command) {// 当工作线程不足时创建新线程(需添加当前工作线程数统计)// 示例代码此处逻辑不完整,建议补充:// if(当前线程数 < maxWorkerCount) {// new Worker(queue).start();// }try {queue.put(command); // 将任务加入队列(阻塞直到添加成功)} catch (InterruptedException e) {Thread.currentThread().interrupt();}}public static void main(String[] args) throws InterruptedException {MyThreadPool pool = new MyThreadPool();// 提交示例任务pool.submit(() -> {System.out.println("吃饭"); // 具体任务逻辑});Thread.sleep(1000); // 主线程等待}
}// 总结-线程安全实现要点:
// 1. 使用LinkedBlockingQueue保证队列操作的线程安全性
// 2. take()/put()方法的阻塞特性自然实现线程等待
// 3. 工作线程通过共享队列实现任务分配
// 4. InterruptedException处理保证线程终止能力
四、总结
Java线程池通过ThreadPoolExecutor
提供高度可定制的并发控制能力。理解每个参数对系统性能的影响(如队列容量与最大线程数的平衡),能够帮助开发者构建出高吞吐、低延迟的线程管理体系。建议根据具体业务场景,结合监控数据动态调整参数,方能发挥线程池的最大效能。