最近看了点 psi-Probe的源代码,在线程列表页面,可以对页面中各个进行线程管理,其中有这样一个操作,见最左侧蓝色方框:
点击每个线程对应的箭头按钮,会弹出下方的提示:
实际这上按钮的操作,是要 「Kill」这个指定的线程。
顺着链接,我们能看到,具体的实现是这个样子:
String threadName = ServletRequestUtils.getStringParameter(request, "thread", null); Thread thread = null; if (threadName != null) { thread = Utils.getThreadByName(threadName); } if (thread != null) { thread.stop(); }
正如前面的弹窗提示,这里果然调用的是个危险操作:
Thread.stop()
这里的 「stop」方法,和「resume」方法、「suspend」方法并称 Thread 三少,因为线程安全问题,都已经被 @Deprecated 了。
官方文档说的好:
Stopping a thread causes it to unlock all the monitors that it has locked
当我们停止一个线程时,它会悄悄的把所持有的 monitor 锁释放了,此时,其他依赖锁的线程可能就会抢到锁执行。关键此时,当前 stop 的线程实际并没有处理完所有先决条件,可能这个时候就产生了诡异的问题,加班的日子可能就悄悄来了。
那你说 「Stop 不让用了,总得让我们有办法处理线程吧,哪怕通知他,打断他一下,让他停止」。
目前有以下几种方式来实现。
异常
这点 Thread 也想到了,提供了一个「异常」来达到这个打断的目的。这个异常在其他线程要打断某个特定线程时执行,如果是符合条件,会抛出来。此时这个特定线程自行根据这次打断来判断后续是不是要再执行线程内的逻辑,还是直接跳出处理。
这个异常就是 InterruptedException。一般使用方式类似这样
try { Thread.sleep(backgroundProcessorDelay * 1000L); } catch (InterruptedException e) { // 具体在中断通知后的操作 } xxxThread.interrupt();
目前有以下方法能够进行这种操作
Thread.sleep
Thread.join
Object.wait
以wait方法为例,我们来看文档里的描述
* @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The <i>interrupted * status</i> of the current thread is cleared when * this exception is thrown.
这里有一点信息: 「interrupted status」,这个是个状态标识,在Thread类中,可以通过 isInterrupted来判断当前线程是否被中断。这个标识也可以用来作为一个退出线程执行的标识来直接使用。 但例外是阻塞方法在收到中断方法调用后,这个标识会被清除重置,所以需要注意下。
我们在执行阻塞方法线程的interrupt方法时,此时并不能拿到这个标识。
另外,拿到异常时,需要关注,如果是类似于后台循环执行的调度线程,在收到中断异常时需要处理异常再 break 才能跳出,否则只是相当于一个空操作。
目前一些程序里用这种的倒不多,用下面这种的多一些。
退出标识
对于一些长驻线程,会在某些时候需要退出执行,这种情况下,常采用的操作类似这样, 以Tomcat 的NioConnector 里的Acceptor为例:
protected class Acceptor extends AbstractEndpoint.Acceptor { @Override public void run() { int errorDelay = 0; // Loop until we receive a shutdown command while (running) { // 标识1 // Loop if endpoint is paused while (paused && running) { // 标识2 state = AcceptorState.PAUSED; try { Thread.sleep(50); } catch (InterruptedException e) { // Ignore } } if (!running) { break; } ... }
用这种退出标识时,记得一定要声明为 volatile ,类似这样:
/** * Running state of the endpoint. */ protected volatile boolean running = false; /** * Will be set to true whenever the endpoint is paused. */ protected volatile boolean paused = false;
否则因为多线程的可见性问题, 这个线程可能一直都不会退出。
目前在 Tomcat 使用中,无法在运行时直接操作 Connector ,所以一般情况这个 pause 标识可能没法设置。但有几种触发的方式,一种是通过 JConsole 等工具连接到 MBeanServer 上,直接通过其MBean方法操作pause,来改变值,另一种是使用类似 psi-Probe(一款功能强大的Tomcat 管理监控工具)这种管理控制台,之前我已经把可以操作 Connector 状态的代码提交给 github上(怎样参与到全世界优秀的开源项目中?),commiter 已经合入。可以使用进行状态改变观察。
总体来说,如果处理sleep/wait等操作,担心时间太长,可以通过 interrupt 来进行,对于驻留线程,可以通过退出标识来处理。
多线程相关送书活动进行中,一图胜千言|谈谈设计模式与Java多线程的学习 | 文末送书
历史热文,请看这里:
相关阅读:
觉得本文对你有帮助?请分享给更多人支持一下,谢谢
关注『 Tomcat那些事儿 』 ,发现更多精彩文章!了解各种常见问题背后的原理与答案。深入源码,分析细节,内容原创,欢迎关注。
加入知识星球,一起进步
更多精彩内容:
一台机器上安装多个Tomcat 的原理(回复001)
监控Tomcat中的各种数据 (回复002)
启动Tomcat的安全机制(回复003)
乱码问题的原理及解决方式(回复007)
Tomcat 日志工作原理及配置(回复011)
web.xml 解析实现(回复 012)
线程池的原理( 回复 014)
Tomcat 的集群搭建原理与实现 (回复 015)
类加载器的原理 (回复 016)
类找不到等问题 (回复 017)
代码的热替换实现(回复 018)
Tomcat 进程自动退出问题 (回复 019)
为什么总是返回404? (回复 020)
...
PS: 对于一些 Tomcat常见问题,在公众号的【常见问题】菜单中,有需要的朋友欢迎关注查看。