大家好,我是码哥
有读者分享小米 Java 后端面试,其中有一个问题,当时没有回答好:什么是线程池、工作原理是什么、线程池可以动态修改吗?
回答这个问题之前,首先我们来了解下什么是线程池,它的工作原理是什么。
线程池(Thread Pool)是一种基于池化思想管理线程的工具,它维护多个线程。在线程池中,总有几个活跃线程。当需要使用线程来执行任务时,可以从池子中随便拿一个空闲线程来用,当完成工作时,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。
然后,我们来看下线程池有哪些状态呢?
线程池有五种状态:这五种状态并不能任意转换,只会有以下几种转换情况:线程池的五种状态是如何流转的?
如何自定义一个线程池?
public ThreadPoolExecutor threadPoolExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
// 核心线程池大小,表示线程池常驻线程数量
30,
// 最大线程数,表示线程池最多创建的线程数量
100,
// 保活时间,表示一个非核心线程多久没有使用,会被回收
10,
TimeUnit.MINUTES,
// 阻塞队列,表示队列最多缓存多少任务,如果队列满了,将触发 RejectedExecutionHandler
new ArrayBlockingQueue<>(1000),
// 线程工厂,创建线程时候用的,可以给线程命名等
new NamedThreadFactory("cust-task")
);
// 拒绝策略,当阻塞队列满了之后,会触发这里的handler
// 默认是丢弃新任务
executor.setRejectedExecutionHandler((r, executor1) -> {
log.warn("thread pool is full");
});
}
线程池执行流程图
注意:提交一个 Runnable 时,不管当前线程池中的线程是否空闲,只要数量小于核心线程数就会创建新线程。
ThreadPoolExecutor 内部有实现 4 个拒绝策略:
好了,言归正传,再回归到这个题目本身,在修改线程池之前,我们要如何监控线程池的信息呢?
比如线程池的执行任务前后总时间,当前任务数等信息。
监控核心代码
@Slf4j
public class ThreadPoolMonitor {
private final ThreadPoolExecutor customThreadPool;
private final String poolName;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public ThreadPoolMonitor(ThreadPoolExecutor customThreadPool, String poolName) {
this.customThreadPool = customThreadPool;
this.poolName = poolName;
}
public void startMonitoring(long period, TimeUnit unit) {
scheduler.scheduleAtFixedRate(this::monitor, 0, period, unit);
}
private void monitor() {
//核心线程数
int corePoolSize = customThreadPool.getCorePoolSize();
//最大线程数
int maximumPoolSize = customThreadPool.getMaximumPoolSize();
//活跃线程数
int activeCount = customThreadPool.getActiveCount();
//队列任务数
int queueSize = customThreadPool.getQueue().size();
//已执行完成任务数
long completedTaskCount = customThreadPool.getCompletedTaskCount();
//队列任务数峰值
int largestPoolSize = customThreadPool.getLargestPoolSize();
//上报监控数据
sendToKafka(corePoolSize,maximumPoolSize, activeCount, queueSize, completedTaskCount, largestPoolSize);
}
private void sendToKafka(int corePoolSize,int maximumPoolSize, int activeCount, int queueSize, long completedTaskCount, int largestPoolSize) {
// 自定义实现发送kafka逻辑或上报到prometheus逻辑
}
}
一般我们在设置线程池的线程数时,会参考实际业务场景。比较通用的公式是
但这只是比较简单粗暴的计算方式,在实际使用过程中,我们还是不可避免的需要调整线程池的一些参数,以达到最佳性能。
那么我们通过会比较关注线程池以下的几个参数
线程池参数 | 说明 |
---|---|
corePoolSize | 核心线程数 |
maximumPoolSize | 最大线程数 |
queueCapacity | 等待队列大小 |
keepAliveTime | 空闲时间 |
最后可以通过 Apollo、Nacos 配置中心实现动态监听的方法,达到实时更新线程池的效果。
扩展 1:线程池核心线程数会被销毁吗?
扩展 2:线程发生异常,会被移出线程池吗?
以上,就是今天的分享,希望对大家有帮助。