乐趣区

为什么建议不使用-stop-方法停止线程

保留好你做过的所有源文件——那是你最好的积攒之一。

线程启动结束后,在运行时可能须要终止,Java 提供的终止办法只有一个 stop,然而我不倡议应用这个办法,因为它有以下三个问题:

(1)stop 办法是过期的从 Java 编码规定来说,曾经过期的办法不倡议采纳。

(2)stop 办法会导致代码逻辑不残缺 stop 办法是一种“歹意”的中断,一旦执行 stop 办法,即终止以后正在运行的线程,不论线程逻辑是否残缺,这是十分危险的。看如下的代码:

public static void main(String[] args) throws Exception {
    // 子线程
    Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                // 子线程休眠一秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {// 异样解决}
        }
    };
    // 启动线程
    thread.start();
    // 主线程休眠一秒
    Thread.sleep(1000);
    // 子线程进行
    thread.stop();}

这段代码的逻辑是这样的:子线程是一个匿名外部类,它的 run 办法在执行时会休眠 1 秒钟,而后再执行后续的逻辑,而主线程则是休眠 0.1 秒后终止子线程的运行,也就是说,JVM 在执行 thread.stop() 时,子线程还在执行 sleep(1000),此时 stop 办法会革除栈内信息,完结该线程,这也就导致了 run 办法的逻辑不残缺,输入语句 println 代表的是一段逻辑,可能十分重要,比方子线程的主逻辑、资源回收、情景初始化等,然而因为 stop 线程了,这些就都不再执行,于是就产生了业务逻辑不残缺的状况。

这是极度危险的,因为咱们不晓得子线程会在什么时候被终止,stop 连根本的逻辑完整性都无奈保障。而且此种操作也是十分荫蔽的,子线程执行到何处会被敞开很难定位,这为当前的保护带来了很多麻烦。

(3)stop 办法会毁坏原子逻辑多线程为了解决共享资源抢占的问题,应用了锁概念,防止资源不同步,然而正因而起因,stop 办法却会带来更大的麻烦:它会抛弃所有的锁,导致原子逻辑受损。例如有这样一段程序:

class MultiThread implements Runnable {
    int a = 0;

    @Override
    public void run() {
        // 同步代码块,保障原子操作
        synchronized ("") {
            a++; // 自增
            try {
                // 线程休眠 0.1 秒
                Thread.sleep(100);
            } catah(InterruptedException e) {e.printStackTrace();
            }
            a--; // 自减
            String tn = Thread.currentThread().getName();
            System.out.println(tn + ":a=" + a);
        }
    }
}

MultiThread 实现了 Runnable 接口,具备多线程能力,其中 run 办法中加上了 synchronized 代码块,示意外部是原子逻辑,它会先自增而后再自缩小,依照 synchronized 同步代码块的规定来解决,此时无论启动多少个线程,打印进去的后果都应该是 a =0,然而如果有一个正在执行的线程被 stop,就会毁坏这种原子逻辑,代码如下:

public static void main(String[] args) {MultiThread thread = new MultiThread();
    Thread thread1 = new Thread(thread);
    // 启动 thread1 线程
    thread1.start();
    for (int i = 0; i < 5; i++) {new Thread(thread).start();}
    // 进行 thread1 线程
    thread1.stop();}

首先要阐明的是所有线程共享了一个 MultiThread 的实例变量 t,其次因为在 run 办法中退出了同步代码块,所以只能有一个线程进入到 synchronized 块中。此段代码的执行程序如下:

1)线程 t1 启动,并执行 run 办法,因为没有其余线程持同步代码块的锁,所以 t1 线程执行自加后执行到 sleep 办法即开始休眠,此时 a =1。

2)JVM 又启动了 5 个线程,也同时运行 run 办法,因为 synchronized 关键字的阻塞作用,这 5 个线程不能执行自增和自减操作,期待 t1 线程锁开释。

3)主线程执行了 t1.stop 办法,终止了 t1 线程,留神,因为 a 变量是所有线程共享的,所以其余 5 个线程取得的 a 变量也是 1。

4)其余 5 个线程顺次取得 CPU 执行机会,打印出 a 值。

剖析了这么多,置信读者也明确了输入的后果,后果如下:

Thread-5:a=1
Thread-4:a=1
Thread-3:a=1
Thread-2:a=1
Thread-1:a=1

本来冀望 synchronized 同步代码块中的逻辑都是原子逻辑,不受外界线程的烦扰,然而后果却呈现原子逻辑被毁坏的状况,这也是 stop 办法被废除的一个重要起因:毁坏了原子逻辑。

既然终止一个线程不能应用 stop 办法,那怎样才能终止一个正在运行的线程呢?答案也很简略,应用自定义的标记位决定线程的执行状况,代码如下:

class SafeStopThread extends Thread {
    // 此变量必须加上 volatile
    private volatile boolean stop = false;

    @Override
    public void run() {
        // 判断线程体是否运行
        while (stop) {// Do Something}
    }
    // 线程终止
    public void terminate() {stop = true;}
} 

这是很简略的方法,在线程体中判断是否须要进行运行,即可保障线程体的逻辑完整性,而且也不会毁坏原子逻辑。可能有读者对 Java API 比拟相熟,于是提出疑难:Thread 不是还提供了 interrupt 中断线程的办法吗?这个办法可不是过期办法,那能够应用吗?它能够终止一个线程吗?

十分好的问题,interrupt,名字看上去很像是终止一个线程的办法,然而我能够很明确地通知你,它不是,它不能终止一个正在执行着的线程,它只是批改中断标记而已,例如上面一段代码:

public static void main(String[] args) {Thread thread = new Thread() {public void run() {
            // 线程始终运行
            while (true) {System.out.println("Running...");
            }
        }
    };
    // 启动 thread 线程
    thread.start();
    // 中断 thread 线程
    thread.interrupt();}

执行这段代码,你会发现始终有 Running 在输入,永远不会进行,仿佛执行了 interrupt 没有任何变动,那是因为 interrupt 办法不能终止一个线程状态,它只会扭转中断标记位(如果在 t1.interrupt() 前后输入 t1.isInterrupted() 则会发现别离输入了 false 和 true),如果须要终止该线程,还须要自行进行判断,例如咱们能够应用 interrupt 编写出更加简洁、平安的终止线程代码:

class SafeStopThread extends Thread {
    @Override
    public void run() {
        // 判断线程体是否运行
        while (!isInterrupted()) {// Do Something}
    }
}

总之,如果冀望终止一个正在运行的线程,则不能应用曾经过期的 stop 办法,须要自行编码实现,如此即可保障原子逻辑不被毁坏,代码逻辑不会出现异常。当然,如果咱们应用的是线程池(比方 ThreadPoolExecutor 类),那么能够通过 shutdown 办法逐渐敞开池中的线程,它采纳的是比拟温和、平安的敞开线程办法,齐全不会产生相似 stop 办法的弊病。

退出移动版