原因
最近在完善公司的基础发布平台的时候, 使用到了一线程去做一些异步的事情, 在开发环境和测试环境验证没有任何问题, 但是在程序在生产运行一段时间后, 发现没有得到自己想要的结果, 为此开始了漫长的排查 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 很好的解决了这个问题
来源: https://www.cnblogs.com/clovejava/p/10053916.html