正常情况下,会优先采用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. if (await) {
  24. await();
  25. stop();
  26. }
  27. }

首先需要清楚,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. try {
  10. Thread.sleep(1000 * 3);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }

这里启动了一个非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这些线程的。

从Tomcat无法正常关闭讲讲Java线程关闭问题【转载】的更多相关文章

  1. 50 道 Java 线程面试题(转载自牛客网)

    下面是 Java 线程相关的热门面试题,你可以用它来好好准备面试. 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理 ...

  2. java 线程关闭小结(转)

    首先,要说的是java中没有一种停止线程的方法是绝对安全的.线程的中断Thread.interrput()方法很容易给人一种误会,让人感觉是一个线程使另外一个正在运行的线程停止工作,但实际上inter ...

  3. 深入了解java线程池(转载)

    出处:http://www.cnblogs.com/dolphin0520/ 本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责 ...

  4. java线程安全之并发Queue

    关闭 原 java线程安全之并发Queue(十三) 2017年11月19日 23:40:23 小彬彬~ 阅读数:12092更多 所属专栏: 线程安全    版权声明:本文为博主原创文章,未经博主允许不 ...

  5. 如何优雅的关闭Java线程池

    面试中经常会问到,创建一个线程池需要哪些参数啊,线程池的工作原理啊,却很少会问到线程池如何安全关闭的. 也正是因为大家不是很关注这块,即便是工作三四年的人,也会有因为线程池关闭不合理,导致应用无法正常 ...

  6. Java线程池理解及用法

    前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory ...

  7. Java线程池原理解读

    引言 引用自<阿里巴巴JAVA开发手册> [强制]线程资源必须通过线程池提供,不允许在应用中自行显式创建线程. 说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销 ...

  8. 【Java并发专题之二】Java线程基础

    使用线程更好的提高资源利用率,但也会带来上下文切换的消耗,频繁的内核态和用户态的切换消耗,如果代码设计不好,可能弊大于利. 一.线程 进程是分配资源的最小单位,线程是程序执行的最小单位:线程是依附于进 ...

  9. java线程池实践

    线程池大家都很熟悉,无论是平时的业务开发还是框架中间件都会用到,大部分都是基于JDK线程池ThreadPoolExecutor做的封装, 都会牵涉到这几个核心参数的设置:核心线程数,等待(任务)队列, ...

随机推荐

  1. Python 编程核心知识体系-模块|面向对象编程(三)

    模块 面向对象编程

  2. L226

    The dean can’t see you at the moment. He is addressing the first-year students in the lecture hall.系 ...

  3. 移动前端开发之viewport的深入理解 --- 待续

    在移动设备上进行网页的重构或开发, 只有明白了viewport的概念 才能 响应 各种  不同分辨率 的移动设备 一.viewport的概念 通俗的讲,移动设备上的viewport就是设备的屏幕上能用 ...

  4. 使用 Koa + MongoDB + Redis 搭建论坛系统

    koa 相对于 express 的优势在于, 1.  使用了 yield generator 封装了co 框架, 使得异步处理, 能像同步那样书写 2.  使用了 中间件 ko-schema, 使得验 ...

  5. 构建你自己的论坛,基于windows服务器的xampp+discuz论坛

    首先声明,论坛的构建需要基于企业,并且基于企业注册,然后进行域名备案. 此处作为研究,先示例: 1. 安装 xampp 软件,百度搜索 然后在服务器安装,基本都是默认安装,然后出来. 安装完成后,接下 ...

  6. magento增加左侧导航栏

    1.打开 app\design\frontend\default\modern\layout\catalog.xml,在适当位置加入以下代码: <reference name=”left”> ...

  7. PyTorch 数据集类 和 数据加载类 的一些尝试

    最近在学习PyTorch,  但是对里面的数据类和数据加载类比较迷糊,可能是封装的太好大部分情况下是不需要有什么自己的操作的,不过偶然遇到一些自己导入的数据时就会遇到一些问题,因此自己对此做了一些小实 ...

  8. gem install没有反应 解决办法

    在虚拟机上执行gem install redis 没有反应: 百度方法如下: #移除官方镜像,增加淘宝镜像 gem sources --add https://ruby.taobao.org/ --r ...

  9. 用pthon来写个跳板机

    用pthon来写个跳板机   1.需求 程序一:1.后台管理- 堡垒机上创建用户和密码(堡垒机root封装的类,UserProfile表)- .bashrc /usr/bin/python3 /dat ...

  10. MySQL--忘记MYSQL管理员密码

    如root用户密码,可以按照以下方式来修改: STEP1: 停止MySQL服务 ps -ef | grep -v 'grep' | grep 'mysqld' | awk '{print $2}' | ...