从Tomcat无法正常关闭讲讲Java线程关闭问题【转载】

正常情况下,会优先采用catalina.sh stop来停止Tomcat实例,这样可以让服务有机会处理完请求,并做好善后工作。 但如果通过catalina.sh stop命令无法关闭Tomcat实例,则只能kill -9了。

为什么在给Tomcat发出stop命令以后,Tomcat实例无法关闭?

可能有两种原因:

  • Tomcat的主线程没有结束(也即main函数没有执行结束);
  • Tomcat中启动的webapps有非daemon线程阻止了Tomcat进程的关闭;

第一种情况,如果发出stop命令以后,Tomcat主线程并没有结束,自然通过它启动的webapps也是无法关闭的。虽然可以确信Tomcat不可能有这个问题,但还是拉出Tomcat的关闭过程代码看看:

  1. public void start() {
  2. if (getServer() == null) {
  3. load();
  4. }
  5. if (getServer() == null) {
  6. log.fatal("Cannot start server. Server instance is not configured.");
  7. return;
  8. }
  9. long t1 = System.nanoTime();
  10. // Start the new server
  11. try {
  12. getServer().start();
  13. } catch (LifecycleException e) {
  14. log.fatal(sm.getString("catalina.serverStartFail"), e);
  15. try {
  16. getServer().destroy();
  17. } catch (LifecycleException e1) {
  18. log.debug("destroy() failed for failed Server ", e1);
  19. }
  20. return;
  21. }
  22.  
  23. ……//省略
  24.  
  25. if (await) {
  26. await();
  27. stop();
  28. }
  29. }

首先需要清楚,Tomcat的正常关闭是通过socket发送命令的方式来触发的,Tomcat在启动完成以后会通过await()一直等待,知道接收到shutdown命令后退出,执行后面的stop()关闭Tomcat实例。

此后不会再有任何await,所以说Tomcat主线程是会正常关闭的。如果不相信,可以开启jpda debug一下,我就这么干了。

既然第一种可能不存在,那只能是webapps中有非daemon线程没有正常关闭了。为什么会这样?因为非Daemon线程被认为是工作线程,必须要主动关闭,而daemon线程属于后台线程,在非daemon线程关闭以后,daemon线程会自动关闭,典型的以main函数为入口的主线程便是非daemon的工作线程。

一个示例:

  1. public static void main(String[] args) {
  2. Thread thread = new Thread(() -> {
  3. while(true){
  4. LockSupport.parkNanos(1000 * 1000 * 3);
  5. }
  6. });
  7. thread.setDaemon(true);
  8. thread.start();
  9.  
  10. try {
  11. Thread.sleep(1000 * 3);
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. }

这里启动了一个非daemon线程,所以即使主线程执行完成以后,应用还是不会正常关闭,如果把线程改成daemon则会。

基于这个原理可以开始定位为什么Tomcat示例无法关闭了,可以通过jstack看看还有那些线程还在运行,之后逐一排除。

最终发现是Java的线程池引起的,用Executors new了一个线程池,因为默认情况下,Executors使用了它自己的默认ThreadFactory,这个东西有毒,它new出来的线程是这样的:

  1. public Thread newThread(Runnable r) {
  2. Thread t = new Thread(group, r,
  3. namePrefix + threadNumber.getAndIncrement(),
  4. 0);
  5. if (t.isDaemon())
  6. t.setDaemon(false);
  7. if (t.getPriority() != Thread.NORM_PRIORITY)
  8. t.setPriority(Thread.NORM_PRIORITY);
  9. return t;
  10. }

这些线程都是非daemon的,所以通过这个线程池submit了任务以后,如果不主动调用线程池的shutdown()函数是无法destroy这些线程的。

原文地址:https://www.cnblogs.com/devilwind/p/6864500.html