ThreadPoolExecutor源码思考
ThreadPoolExecutor自定义线程工厂
ThreadPoolExecutor线程池采用ThreadFactory中默认的DefaultThreadFactory实现类来创建的,使用工厂方法模式来设计的线程工厂创建线程。(而工厂方法模式对于我的理解:一个产物对应一个工厂)
static class InnerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyyMMd HH:mm:ss");
thread.setName(THREAD_PREFIX + ldt.format(pattern));
return thread;
}
}
ThreadPoolExecutor任务饱和策略
- AbortPolicy:直接抛出RejectedExecutionException异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉。
线程池什么时候启动
在new ThreadPoolExecutor类时并没有启动线程池,只是设置了参数,而线程池的启动是在执行execute方法(源码中是addWorker方法)。
线程池生命周期
任务执行机制
任务机制1,任务调度
任务机制2,任务缓冲
任务机制3,任务申请
getTask方法:
任务机制4,任务拒绝
- AbortPolicy:直接抛出RejectedExecutionException异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉。
Worker线程管理机制
线程增加
addWorker方法:
线程回收
processWorkerExit方法:
线程执行任务
runWorker方法:
线程释放策略
- 第一种策略:使用allowCoreThreadTimeOut方法对核心线程和非核心线程进行释放(调用poll方法,超出了keepAliveTime,自动释放)。
- 第二种策略:使用shutdown方法关闭线程池来对核心线程和非核心线程进行释放(主动为Workers集合中的线程设置中断标识,自动释放)。
设置allowCoreThreadTimeOut为true或false,对线程回收影响
- 如果设置allowCoreThreadTimeOut为true,那么不管你是否为核心线程还是非核心线程,到指定的keepAliveTime时间,那么就直接不阻塞,执行processWorkerExit方法释放掉。
- 如果设置allowCoreThreadTimeOut为false,那么还需要判断如果没超过核心线程数,那么直接当前工作线程阻塞,如果超过核心线程数,那么直接将当前线程,按照指定的keepAliveTime时间,这段时间阻塞,到时间点了,那么不阻塞了,执行processWorkerExit方法释放掉。
设置allowCoreThreadTimeOut为true,内部如何优雅的停掉线程
- 先获取锁,然后给所有的线程都打上中断标记,最后释放锁。
当线程池没有任务执行的时候,再进来任务的时候,如何处理的?
-
先判断工作线程数是否达到核心线程数
-
如果工作线程数已经超过了核心线程数,那么之前的工作线程肯定都处于阻塞状态,此时再进来一个任务,创建了一个Worker,又创建添加了新的工作线程,这样我们的任务添加进了阻塞队列(工作线程数 > 核心线程数,进入阻塞队列),阻塞的线程发现来任务了,都开始争抢任务。
-
如果工作线程数没有超过核心线程数,那么来了一个任务,创建了一个Worker,又创建添加了新的工作线程,这样我们的任务直接使用我们自己的新创建的线程执行(工作线程数 < 核心线程数,直接执行)。
-
至于我们是否设置allowCoreThreadTimeOut为true,只是控制核心线程数,如果没到keepAliveTime,可能会出现工作线程数等于核心线程数。如果到了keepAliveTime,可能会出现工作线程数小于核心线程数。
processWorkerExit方法如何回收线程
processWorkerExit方法 -> tryTerminate方法 -> interruptIdleWorkers方法
- 先获取锁,然后给所有的线程都打上中断标记,最后释放锁。
execute方法执行过程
- 1. 工作线程数 < 核心线程数
1.1 new Thread创建新线程作为当前工作线程,将当前工作线程Worker添加到workers集合
1.2 启动当前线程(调用runWorker方法)
1.2.1 获取当前线程,当前线程已执行完毕并释放,那么从阻塞队列获取任务(调用getTask)
1.2.1.1 如果等待队列中存在了等待的任务,那么取出等待队列中第一个任务返回。
1.2.1.2 如果超过核心线程数的线程,那么timed = true,同时队列中已经没有任务了,那么等待keepalivetime秒就释放。
1.2.1.3 如果不超过(等于)核心线程数的线程,那么timed=false,同时队列中已经没有任务了,那么执行take方法阻塞。
1.2.1.3.1 如果此时进来一个新任务,那么发现阻塞线程数(工作线程数)不小于核心线程数,那么直接执行offer插入等待队列,等待队列有值了,阻塞的线程们又开始抢任务干活了。
1.2.2 执行我们自己的业务逻辑(调用run方法)
1.2.3 移除workers中的当前线程,回收线程(调用processWorkerExit方法)
1.3 移除workers中的当前线程,回收线程(调用addWorkerFailed方法) - 2. 工作线程数 > 核心线程数
将当前线程加入到等待队列BlockQueue。 - 3. 等待队列满了
直接创建新线程作为工作线程执行逻辑。 - 4. 工作线程数 > 最大线程数
抛出拒绝策略异常
线程池中线程异常处理
public class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 进入了task方法!!!");
int i=1/0;
}
}
execute会抛出异常
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
keepAliveSeconds, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity), new InnerThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
});
executor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);
//当线程池抛出异常后 execute抛出异常,其他线程继续执行新任务
executor.execute(new Task());
}
submit会在返回时抛出异常
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
keepAliveSeconds, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity), new InnerThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
});
executor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);
//当线程池抛出异常后 submit无提示,其他线程继续执行
executor.submit(new Task());
}
线程池中多线程下载任务,其中出现一些线程抛出异常,事务回滚如何处理?
- 思路:可以将每次线程执行成功的操作记录到数据库中,然后当所有的线程任务执行完毕,那么将最终的执行结果还原,保证最终一致性。
生产上如何配置使用线程池
代码
/**
* 线程池配置类
* @author sunpeiyu
* @date 2023-04-10
*/
@Configuration
public class ThreadConfig {
/**
* 最大线程数量
*/
private static final int maxPoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
/**
* 核心线程数量
*/
private static final int corePoolSize = maxPoolSize - 1;
/**
* 空闲线程存活时间
*/
private static final int keepAliveSeconds = 60;
/**
* 线程阻塞队列容量
*/
private static final int queueCapacity = 1000;
/**
* 是否允许核心线程超时
*/
private static final boolean allowCoreThreadTimeOut = false;
/**
* 线程前缀
*/
private static final String THREAD_PREFIX = "DOWNLOADER_";
@Bean
@Scope("singleton")
public ThreadPoolExecutor getExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
keepAliveSeconds, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity), new InnerThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
});
executor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);
return executor;
}
static class InnerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyyMMd HH:mm:ss");
thread.setName(THREAD_PREFIX + ldt.format(pattern));
return thread;
}
}
}
线程池预热
prestartAllCoreThreads()
启动所有核心线程,导致他们等待工作。 这将覆盖仅在执行新任务时启动核心线程的默认策略。
prestartCoreThread()
启动核心线程,使其无法等待工作。 这将覆盖仅在执行新任务时启动核心线程的默认策略。 如果所有核心线程已经启动,此方法将返回false 。
线程池监控
参考
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html