原因
最近在完善公司的基础发布平台的时候,使用到了一线程去做一些异步的事情,在开发环境和测试环境验证没有任何问题,但是在程序在生产运行一段时间后,发现没有得到自己想要的结果,为此开始了漫长的排查bug的之路,因为用到了一些线程,但是实际又没有对这些线程足够的监控,所以在排查问题的时候也是历经艰难险阻;
原始代码
protected ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
/**
* 同步应用的jenkins状态
*/
public void threadASyncAppJenkinsStatus() {
executorService.scheduleAtFixedRate(() -> {
List<ArchitectureApp> architectureApps = architectureAppMapper.listArchitectureApp();
architectureApps.parallelStream().forEach(architectureApp -> syncJenkinsBuild(architectureApp.getName(), ArchitectureType.APP));
}, 0, 6, TimeUnit.SECONDS);
}
/**
* 同步组件的jenkins状态
*/
public void syncComponentJenkinsStatus() {
executorService.scheduleAtFixedRate(() -> {
List<ArchitectureComponent> architectureComponents = architectureComponentMapper.listArchitectureComponent();
architectureComponents.parallelStream().forEach(architectureComponent -> syncJenkinsBuild(architectureComponent.getName(), ArchitectureType.COMPONENT));
}, 0, 6, TimeUnit.SECONDS);
}
这是其中一部分的代码,做的事情很简单,程序每隔6s就去轮询组件和应用的状态,然后后面我会通过websocket同步到前端页面。这是一段很简单的代码,很难想象这段代码可能出错。但是事与愿违,通过开发和测试环境的测试,在上到生产运行了两天发现前端页面的jenkins状态并没有同步。而通过查看日志,也没法观察问题出在哪,所以只能另寻他法;
ExecutorsMonitor线程监控类
以下是我们开发的一个线程池工具类,该工具类扩展ScheduledThreadPoolExecutor实现了线程池监控功能,能实时将线程池使用信息打印到日志中,方便我们进行问题排查、系统调优。具体代码如下
@Slf4j class ExecutorsMonitor extends ScheduledThreadPoolExecutor { private ConcurrentHashMap<String, Date> startTimes; private String poolName; /** * 调用父类的构造方法,并初始化HashMap和线程池名称 * * @param corePoolSize 线程池核心线程数 * @param poolName 线程池名称 */ public ExecutorsMonitor(int corePoolSize, String poolName) { super(corePoolSize); this.startTimes = new ConcurrentHashMap<>(); this.poolName = poolName; } /** * 线程池延迟关闭时(等待线程池里的任务都执行完毕),统计线程池情况 */ @Override public void shutdown() { super.shutdown(); } /** * 线程池立即关闭时,统计线程池情况 */ @Override public List<Runnable> shutdownNow() { return super.shutdownNow(); } /** * 任务执行之前,记录任务开始时间 */ @Override protected void beforeExecute(Thread t, Runnable r) { startTimes.put(String.valueOf(r.hashCode()), new Date()); } /** * 任务执行之后,计算任务结束时间 */ @Override protected void afterExecute(Runnable r, Throwable t) { Date startDate = startTimes.remove(String.valueOf(r.hashCode())); Date finishDate = new Date(); long diff = finishDate.getTime() - startDate.getTime(); // 统计任务耗时、初始线程数、核心线程数、正在执行的任务数量、已完成任务数量、任务总数、队列里缓存的任务数量、池中存在的最大线程数、最大允许的线程数、线程空闲时间、线程池是否关闭、线程池是否终止 log.info(String.format(this.poolName + "-pool-monitor: Duration: %d ms, PoolSize: %d, CorePoolSize: %d, Active: %d, Completed: %d, Task: %d, Queue: %d, LargestPoolSize: %d, MaximumPoolSize: %d, KeepAliveTime: %d, isShutdown: %s, isTerminated: %s", diff, this.getPoolSize(), this.getCorePoolSize(), this.getActiveCount(), this.getCompletedTaskCount(), this.getTaskCount(), this.getQueue().size(), this.getLargestPoolSize(), this.getMaximumPoolSize(), this.getKeepAliveTime(TimeUnit.MILLISECONDS), this.isShutdown(), this.isTerminated())); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, String poolName) { return new ExecutorsMonitor(corePoolSize, poolName); } }
后来在生产终于定位问题,发现线程内部后来停止,同时发现的还有报错,通过查阅资料发现,原来线程发生异常后会退出,通过try catch很好的解决了这个问题