中止线程的方法

作者: 叠最厚的甲 | 来源:发表于2019-04-30 20:59 被阅读5次

    中止线程的方法

    如何中止一个正在执行的线程?

    Thread#stop()

    java.lang.Thread#stop强制线程停止执行

    从JDK1.2开始,该API已被弃用,因为它可能导致线程安全问题

    Thread#stop()方法通过抛出java.lang.ThreadDeath 异常来达到中止线程的目的。这会使线程释放它持的有全部锁,如果之前被锁保护的对象已经处于不一致状态,那么这些状态将立即对其它线程可见。当其它线程操作一个损坏的对象时,将会导致不可预测的后果!

    而且,默认情况下,ThreadDeath错误不会被打印或通知应用程序。在 java.lang.ThreadGroup中,可以看到这样一段代码:

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);         
            } 
            // ThreadDeath异常被忽略
            else if (!(e instanceof ThreadDeath)) { // 调用Thread#stop()方法时,可在此处断点调试
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
    

    这意味着,如果你使用stop()方法中止线程,你的应用不会收到任何通知。此时,你的某些对象可能已经处于不一致状态,然而你对此一无所知。

    能否通过捕获ThreadDeath异常来处理这种情况呢?理论上是可以的,但是不推荐这么做。原因有二:

    • ThreadDeath异常可能在线程执行的任何地方抛出。那么,所有的同步方法和同步代码块都需要对ThreadDeath异常捕获处理。
    • catchfinally子句中处理第一个ThreadDeath异常时,可能会有第二个ThreadDeath抛出,必须重复处理直到成功。

    这样做的代价太大,不现实。

    既然Thread#stop()已经被弃用了,那么有没有什么替代方案可以中止线程呢?

    基于状态的信号机制

    在大部分使用Thread#stop()的地方都可以用基于状态的信号机制来替代。

    在线程之间共享一个变量,目标线程定期地去检查这个变量的状态并据此判断是否应该继续执行,而其它线程就可以通过修改这个状态来中止目标线程。

    public class StatusBasedSignalStop {
    
        private static volatile boolean isRunning = true;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    // 检查变量的值,判断是否应该中止执行
                    while (isRunning) {
                        System.out.println("线程状态: " + Thread.currentThread().getState());
                        try {
                            Thread.sleep(1100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 退出执行前可以做一些善后工作,比如资源清理
                }
            });
            thread.start();
            // 3s后修改变量的值,表明目标线程应该中止了
            Thread.sleep(3000);
            isRunning = false;
            // 休眠1s,再看目标线程的状态
            Thread.sleep(1000);
            System.out.println("线程状态: " + thread.getState());
        }
    }
    

    程序输出如下:

    线程状态: RUNNABLE
    线程状态: RUNNABLE
    线程状态: RUNNABLE
    线程状态: TERMINATED
    

    注意:被线程共享的变量必须使用volatile关键字修饰,或者在同步方法或同步代码块中操作,这样才能保证变量的修改对其它线程可见。

    Thread#interrupt()

    基于状态的信号机制可以满足大部分线程中止场景,但是当目标线程处于长时间等待状态(比如在一个条件上等待)时,该机制无法使用。此时,可以尝试使用Thread#interrupt()去打断它。

    public class InterruptStop {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {
                        // 我想睡好久好久
                        Thread.sleep(Long.MAX_VALUE);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                        // Thread#sleep()抛出InterruptedException会清除中断标志,此处再次中断线程
                        Thread.currentThread().interrupt();
                    }
                }
            });
            thread.start();
            // 主线程休眠1s,等待目标线程进入睡眠
            Thread.sleep(1000);
            // 调用目标线程的interrupt()方法中断等待
            thread.interrupt();
            // 等待目标线程执行完毕
            thread.join();
        }
    }
    

    运行main()函数,1秒后主线程退出。这说明Thread#interrupt()成功中止了目标线程的睡眠。

    然而,并不是所有的等待都被Thread#interrupt中止,当目标线程处于以下等待场景时:

    • Object#wait();
    • Object#wait(long);
    • Object#wait(long, int);
    • Thread#join();
    • Thread#join(long);
    • Thread#sleep(long);
    • Thread#sleep(long, int);
    • java.nio.channels.InterruptibleChannel上的阻塞IO操作;
    • java.nio.channels.Selector上的阻塞IO操作;

    调用Thread#interrupt方法会中断等待(不同的情景响应中断后线程的中断标志不同)。其他情况下调用Thread#interrupt只会设置目标线程的中断标志,无法中止等待状态。此时,只能根据具体场景来分析如何中止等待(比如,如果线程在Socket上长时间等待,可以关闭Socket来中止等待)。

    相关文章

      网友评论

        本文标题:中止线程的方法

        本文链接:https://www.haomeiwen.com/subject/tbdbnqtx.html