线程池 ThreadPoolExecutor
工作流程
- 提交任务时,如果线程池当前线程数量小于核心线程数时,直接创建线程来处理任务(线程池初始不会创建线程,当有任务提交才创建)
- 当线程池线程数量达到核心线程数,任务进入队列
- 当队列满了不能继续放时,再次创建线程来处理任务
- 这时的线程叫救急线程
- 当救急线程处理完任务空闲时,达到存活时间后会销毁救急线程
- 救急线程也会到队列以此获取任务(比如当提交第100个任务时创建的救急线程,但是这个救急线程不会执行第100个任务,而是会到队列里获取任务)
- 核心线程数达到阈值、队列满了、最大线程数也达到阈值,执行拒绝策略
一个变量保存线程个数和状态
// 线程数量
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池最大线程容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 下面5个属性表示线程池不同状态
// 运行中
private static final int RUNNING = -1 << COUNT_BITS;
// 停止中(不是停止,是停止中。不会接收新任务,但是已经收到的任务会继续处理)调用 shotdown() 方法进入该状态
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 停止中(不接受新任务,丢弃已经接收到的任务,正在执行的任务会中断)调用 shotdownNow() 方法进入该状态
private static final int STOP = 1 << COUNT_BITS;
// 过度状态,当活动线程为 0 即将进入终结状态时的状态
private static final int TIDYING = 2 << COUNT_BITS;
// 总结状态,真正的停止
private static final int TERMINATED = 3 << COUNT_BITS;
数量和状态都是一个变量 COUNT_BITS 保存的,高 3 位存储线程状态,低 29 位表示线程数量
为什么要一个变量保存两个信息?定义两个变量不是更用以理解吗?如果两个变量就要保证两次原子操作,放到一个变量里只需要一次原子操作
构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
各个参数解释
参数 | 含义 | 说明 |
---|---|---|
corePoolSize | 核心线程数 | |
maximumPoolSize | 最大线程数 | 核心线程数+救急线程数=最大线程数 |
keepAliveTime | 救急线程存活时间(长度) | 当核心线程和队列都满负荷,会创建救急线程,当救急线程不需要时会被回收 如果队列是无界队列,就不会创建救急线程,但是要谨防内存不足 |
unit | 救急线程存活时间(单位) | |
workQueue | 阻塞队列 | 为什么要阻塞队列?当任务提交和执行速度不一致时: 对于生产者,队列满了不能再接收任务时阻塞任务提交 对于消费者,队列没有任务时,阻塞获取任务,直到有任务进入队列 |
threadFactory | 线程工厂 | 起个好名字方便排查问题,不然都是默认的名字不好区分 |
handler | 拒绝策略 | 当核心线程、队列、救急线程都满负荷时执行 |
拒绝策略
JDK 提供的拒绝策略:
- AbortPolicy:会抛出 RejectedExcutionException 异常(默认策略)
- CallerRunsPolicy:让调用者执行任务(如果不在自己创建线程提交任务,那就是主线程)
- DiscardPolicy:放弃任务,也不会有任何反馈,直接抛弃任务(抛弃新来的任务)
- DiscardOldestPolicy:也是放弃任务,但是和 DiscardPolicy 不一样的是 DiscardOldestPolicy 会抛弃最早的任务(抛弃原有的任务)
三方框架扩展的拒绝策略:
- Dubbo 的实现:扩展 AbortPolicy,在抛出 RejectedExcutionException 前会记录日志,并 dump 线程栈信息
- Netty 的显现:创建一个新的线程来执行任务
- ActiveMQ:带超时等待(60s),时间到了再次尝试放入队列
工厂方法创建线程池
ExecutorService 类提供了一些便捷的方法创建线程池,底层都是利用 ThreadPoolExecutor 构造方法创建的
-
创建固定线程数量的线程池
核心线程数 = 最大线程数,意味着没有救急线程;队列是无界的
// java.util.concurrent.Executors#newFixedThreadPool(int, java.util.concurrent.ThreadFactory) public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
-
创建带缓冲功能的线程池(取名有点词不达意,本质是不带缓存功能的线程池)
核心线程数是0,最大线程数是 Integer.MAX_VALUE,全部都是救急线程(存活时间 60s)
SynchronousQueue 特点是不存放任务,有消费者在等待时才能把任务放入队列,刚放进去消费者就取走了(队列不会缓存任务),原理是调用 offer 或 put 方法时,如果没有等待线程会阻塞
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
-
创建单个线程的线程池
核心线程数 = 最大线程数 = 1,也不会创建救急线程,队列是无界的,任务不会并发执行,一个一个串行执行
返回的是 FinalizableDelegatedExecutorService 不是 ThreadPoolExecutor(装饰器模式),和 ExecutorService.newFixedThreadPool(1) 还是有区别的,ExecutorService.newFixedThreadPool(1) 强转后还是可以修改线程数量
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
提交任务
// 执行 Runnable 任务
void execute(Runnable command);
// 提交 Callable 任务,有返回值
<T> Future<T> submit(Callable<T> task);
// 提交 Runnable 任务,有返回值,内部用 FetureTask 包装了 Runnable
Future<?> submit(Runnable task);
// 批量提交任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 批量提交任务,带超时时间(时间截至时,没有完成的任务会被取消,返回执行完成的)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException;
// 批量提交任务,哪个任务先完成,返回哪个任务的结果,其他任务取消
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 批量提交任务,哪个任务先完成,返回哪个任务的结果,其他任务取消,带超时时间(时间截至时,没有完成的任务会被取消,返回执行完成的)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具