最近看了点 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 来进行,对于驻留线程,可以通过退出标识来处理。
【本文为51CTO专栏作者“侯树成”的原创稿件,转载请通过作者微信公众号『Tomcat那些事儿』获取授权】