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 获取监控数据,然后制作一个可视化界面来展示这些数据,这样更有利于根据监控数据状态进行线程池优化。