Java监控线程池的2种方法(附带示例)
在生产环境中,我们可以实时监控线程池的运行状态,随时掌握应用服务的性能状况,以便在系统资源紧张时及时告警,动态调整线程配置,并在必要时进行人工介入,排查问题,线上修复,也就是说,通过实时监控实现动态修改。
线程池的监控方法分为两种:
下面我们利用 TimingThreadPoolExecutor 工具类监控线程池,方法如下:
下面我们利用 ThreadPoolStatusMonitor 工具类定时监控线程池,在主程序中,我们创建并启动监控线程,方法如下:
我们简化了上述两个监控示例的实现过程,在实际生产中,为了提升性能和实现可视化,可以利用 Kafka 获取监控数据,然后制作一个可视化界面来展示这些数据,这样更有利于根据监控数据状态进行线程池优化。
线程池的监控方法分为两种:
- 一种是全量监控,即在执行任务前后全量统计任务排队时间和执行时间,让我们能够了解每个任务的性能;
- 另一种是定时监控,即通过定时任务,定时获取活跃线程数、队列中的任务数、核心线程数、最大线程数等,能够让我们了解线程池的整体性能和状态。
全量监控线程池
为了实现全量监控,我们可以通过扩展 ThreadPoolExecutor 类并重写 before-Execute() 和 afterExecute() 两个方法来记录时间,示例实现代码如下:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
public class TimingThreadPoolExecutor extends ThreadPoolExecutor {
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
private final AtomicLong totalTime = new AtomicLong();
private final AtomicLong numTasks = new AtomicLong();
public TimingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
startTime.set(System.nanoTime());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
try {
long endTime = System.nanoTime();
long taskTime = endTime - startTime.get();
numTasks.incrementAndGet();
totalTime.addAndGet(taskTime);
System.out.printf("任务耗时: %dns\n", taskTime);
} finally {
super.afterExecute(r, t);
}
}
public void shutdownAndReport() {
System.out.printf("平均任务耗时: %dns\n", totalTime.get() / numTasks.get());
super.shutdown();
}
}
下面我们利用 TimingThreadPoolExecutor 工具类监控线程池,方法如下:
public class TimingThreadPoolExample {
public static void main(String[] args) {
TimingThreadPoolExecutor executor = new TimingThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
executor.submit(() -> {
// 模拟任务执行时间
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
executor.shutdownAndReport();
}
}
在上述示例中,beforeExecute() 方法在任务开始前记录了开始时间,而 afterExecute() 方法在任务结束时计算了任务耗时并将其输出。定时监控线程池
定时监控可以通过一个独立的监控线程来实现,该线程周期性地从线程池获取状态信息并记录或报告这些信息。示例实现代码如下:
import java.util.concurrent.*;
public class ThreadPoolStatusMonitor implements Runnable {
private final ThreadPoolExecutor executor;
private final int monitoringPeriod;
private boolean running = true;
public ThreadPoolStatusMonitor(ThreadPoolExecutor executor, int monito-ringPeriod) {
this.executor = executor;
this.monitoringPeriod = monitoringPeriod;
}
public void shutdown() {
running = false;
}
@Override
public void run() {
while (running) {
System.out.println(
String.format("[监控] 活跃线程:%d, 核心线程数:%d,
最大线程数:%d, 队列任务数:%d, 完成任务数:%d",
executor.getActiveCount(),
executor.getCorePoolSize(),
executor.getMaximumPoolSize(),
executor.getQueue().size(),
executor.getCompletedTaskCount())
);
try {
TimeUnit.SECONDS.sleep(monitoringPeriod);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
下面我们利用 ThreadPoolStatusMonitor 工具类定时监控线程池,在主程序中,我们创建并启动监控线程,方法如下:
public class ThreadPoolMonitoringExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
// 启动线程池状态监控线程,每2s监控一次
ThreadPoolStatusMonitor monitor = new ThreadPoolStatusMonitor(executo-r, 2);
Thread monitorThread = new Thread(monitor);
monitorThread.start();
// 提交一些任务给线程池
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 模拟程序运行一段时间后,关闭监控线程和线程池
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
monitor.shutdown();
executor.shutdown();
}
}
在上述示例中,我们创建了一个 ThreadPoolStatusMonitor 实例监控线程池。它会每隔一定时间输出一次线程池的各种状态信息。通过这种方式,我们可以监控线程池的状态,以确保它正常运行且在运行中保持性能稳定。我们简化了上述两个监控示例的实现过程,在实际生产中,为了提升性能和实现可视化,可以利用 Kafka 获取监控数据,然后制作一个可视化界面来展示这些数据,这样更有利于根据监控数据状态进行线程池优化。
ICP备案:
公安联网备案: