• 从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这些线程的。

  • 相关阅读:
    群辉synology实现百度云盘资源同步
    Java下如何保证多线程安全
    Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
    Java ThreadPool线程池
    多个线程交替执行
    高并发编程Condition深入解析
    哨兵挂了,Redis还能正常工作吗?
    Java开发实用的面试题及参考答案
    浅谈双亲委派机制的缺陷及打破双亲委派机制
    多线程交替执行
  • 原文地址:https://www.cnblogs.com/devilwind/p/6864500.html
Copyright © 2020-2023  润新知