前言
线程池就是管理一系列线程的资源池,其提供了一种限制和管理线程资源的方式。
一、线程池原理及创建
1、线程池创建
方式一:通过ThreadPoolExecutor构造函数来创建(推荐)。
import java.util.concurrent.*;
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
// 创建一个线程工厂,用于创建新的线程
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
// 创建一个阻塞队列,用于存放等待执行的任务
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(1000);
// 创建一个饱和策略,当线程池和阻塞队列都满时,如何处理新的任务
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
// 创建一个线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
10L, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
queue, // 阻塞队列
namedThreadFactory, // 线程工厂
handler // 拒绝策略
);
// 关闭线程池
executor.shutdown();
}
}
方式二:通过 Executor 框架的工具类 Executors 来创建(不推荐)。
因为使用默认的 Executor 框架的工具类创建的线程池使用的是无界的阻塞队列,容易导致OOM
2、线程池参数
- corePoolSize : 任务队列未达到队列容量时,最大可以同时运行的线程数量。
- maximumPoolSize : 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
- workQueue: 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
- keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁。
- unit : keepAliveTime 参数的时间单位。
- threadFactory :创建新线程的时候会用到。
- handler :拒绝策略。
注:拒绝策略方式
AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
DiscardPolicy:不处理新任务,直接丢弃掉。
DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。
3、线程池执行流程
- 如果当前运行的线程数小于核心线程数,那么就会新建一个线程来执行任务;
- 如果当前运行的线程数等于或大于核心线程数,但是小于最大线程数,那么就把该任务放入到任务队列里等待执行;
- 如果向任务队列投放任务失败(任务队列已经满了),但是当前运行的线程数是小于最大线程数的,就新建一个线程来执行任务;
- 如果当前运行的线程数已经等同于最大线程数了,新建线程将会使当前运行的线程超出最大线程数,那么当前任务会被拒绝,拒绝策略会调用RejectedExecutionHandler.rejectedExecution()方法。
二、动态调整线程池参数
可以使用配置中心如Nacos进行动态调整,后续会专门出一篇文章讲解。
三、监控线程池状态
1、使用Java定时任务线程池每隔一段时间打印相关参数
public static void printThreadPoolStatus(ThreadPoolExecutor threadPool) {
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, createThreadFactory("print-images/thread-pool-status", false));
scheduledExecutorService.scheduleAtFixedRate(() -> {
log.info("++++++++++++++++++++++++++++");
log.info("ThreadPool Size: [{}]", threadPool.getPoolSize());
log.info("Active Threads: {}", threadPool.getActiveCount());
log.info("Number of Tasks : {}", threadPool.getCompletedTaskCount());
log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
log.info("++++++++++++++++++++++++++++");
}, 0, 1, TimeUnit.SECONDS);
}
2、使用JMX
JMX(Java Management Extensions)是Java平台的一部分,它提供了一套标准的API,用于管理和监控Java应用程序、设备、系统和服务。JMX允许你通过MBeans(Managed Beans)来暴露和管理资源的状态和行为。MBeans可以是标准的或是自定义的,它们代表了可以被管理的实体。
// 导出线程池的MBean接口
public interface ThreadPoolMonitorMBean {
int getCorePoolSize();
int getActiveCount();
int getQueueSize();
long getCompletedTaskCount();
}
// 实现MBean接口
public class ThreadPoolMonitor implements ThreadPoolMonitorMBean {
private ThreadPoolExecutor threadPool;
public ThreadPoolMonitor(ThreadPoolExecutor threadPool) {
this.threadPool = threadPool;
}
@Override
public int getCorePoolSize() {
return threadPool.getCorePoolSize();
}
@Override
public int getActiveCount() {
return threadPool.getActiveCount();
}
@Override
public int getQueueSize() {
return threadPool.getQueue().size();
}
@Override
public long getCompletedTaskCount() {
return threadPool.getCompletedTaskCount();
}
}
// 在应用程序中注册MBean
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
ThreadPoolMonitor monitor = new ThreadPoolMonitor(threadPool);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example:type=ThreadPoolMonitor");
mbs.registerMBean(monitor, name);
访问MBean:你可以使用JMX客户端工具,如JConsole,通过localhost:port来连接到运行的应用程序,并查看注册的MBeans。在JConsole中,你应该能看到名为com.example:type=MemoryMonitor的MBean及其提供的方法。