Java 踩坑 2|线程池的后台线程异常退出

103 阅读2分钟

背景

某个服务包括后台多个消息队列消费者线程,由线程池管理。由于是消费者线程,业务逻辑都形如while(true),理论上应该一直处于运行状态,但某个环境服务产出异常,查看服务进程正常、但无消费消息日志,日志保留时间有限。此时应该进入消息队列控制台,查看消息消费情况和消费者连接情况,但由于非技术原因无法马上检查,因此只能从服务自身状态去定位。

分析

通过jpsjstack查看了服务进程状态,如下图所示。 image.png

可以看到对应线程池的线程都处于WAITING状态,说明对应业务任务已经执行完成,等待新提交的任务。后续分析是while(true)中某个逻辑导致消费者线程“异常退出”,这里不再赘述。

复现示例代码

import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTest {
    public static void main(String[] args) throws InterruptedException {
        int backThreadCount = 3;
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(backThreadCount, backThreadCount,
                60, TimeUnit.SECONDS, new SynchronousQueue<>(),
                new CustomizableThreadFactory("back-thread-pool-"));
        for(int i=0;i<backThreadCount;i++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " start");
                if(true) {
                    throw new IllegalArgumentException("some exception");
                }
                while (true) {
                    // do something
                }
            });
        }

        while(true) {
            // mock monitor the thread pool, not for prod
            Thread.sleep(5000L);
            System.out.println("threadPoolExecutor activeCount: " + threadPoolExecutor.getActiveCount());
        }
    }
}

控制台输出:

back-thread-pool-1 start
back-thread-pool-3 start
back-thread-pool-2 start
threadPoolExecutor activeCount: 0
threadPoolExecutor activeCount: 0

小结

  • 线程池指定名称非常重要,服务大量使用线程池,需要根据名称定位线程状态;
  • 对于关键的线程池,进行线程池监控非常有必要(如示例代码 28 行所示),能够快速定位问题!
  • 对于消息队列消费者任务/线程,初始化等“不能出现异常”的逻辑应该放在while(true)之外。

其实这个问题很简单,简单使用jpsjstack根据线程状态就能定位,这里仅是简单记录(大佬勿喷)。大家有遇到什么疑难杂症或有趣的问题欢迎在评论区留言贴链接,感谢。