2024-03-23
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/9714441734

回答

在 Java 中要终止一个正在运行的线程,有三种方法:

  1. 使用退出标志。使用一个标志变量来控制线程是否正常退出任务。线程的主循环不断检测这个变量,当变量标志为停止时,线程安全退出。
  2. 利用中断机制。调用线程的 interrupt() 来控制线程中断。
  3. 强制退出。调用线程的 stop() 强行终止线程,不推荐这种方法,

详解

使用退出标志

当我们使用轮询或者调用第三方库失败需要不断重试时,这些类似的场景大部分都存在类似一个这样的循环体 while(true) { ... },一般这种场景就比较适合实用退出标志来结束线程的运行。

具体方法是:设置一个 volatile 修饰的共享变量,当变量为 true 时线程正常运行,当需要中断时,将该共享变量设置为 false。代码如下:

public class MyThread extends Thread {
    volatile Boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 运行");

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 退出线程");
    }
}

测试类:

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();

        // 等待线程完全运行
        TimeUnit.SECONDS.sleep(2);
        System.out.println("线程状态:" + myThread.getState().name());

        TimeUnit.SECONDS.sleep(5);
        myThread.flag = false;
        // 等待线程完全退出
        TimeUnit.SECONDS.sleep(3);
        System.out.println("线程状态:" + myThread.getState().name());
        
        TimeUnit.SECONDS.sleep(5);
    }
}

执行结果:

  • 1、线程状态 TIMED_WAITING。因为线程在 sleep(2)
  • 2、线程状态 TERMINATED,说明线程已执行完 run()退出来了。

这种方式是一种比较优雅的方式,但是需要注意变量的类型。

使用中断机制

有些线程因为 I/O 操作,或者调用了sleep()wait()等类似方法阻塞时,这个时候由于我们线程处于不可运行状态,就无法通过使用退出标志来退出线程了。这个时候我们就可以利用 Thread 的 interrupt()。该方法可以让一个被阻塞的线程抛出一个中断异常 InterruptedException,我们可以捕获该异常,然后退出线程。

public class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 运行");

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 抛出中断异常,退出线程");
                return;
            }
        }
    }
}

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();

        // 等待线程完全运行
        TimeUnit.SECONDS.sleep(2);
        System.out.println("线程状态:" + myThread.getState().name());

        TimeUnit.SECONDS.sleep(5);
        // 中断线程
        myThread.interrupt();
        // 等待线程完全退出
        TimeUnit.SECONDS.sleep(3);
        System.out.println("线程状态:" + myThread.getState().name());

        TimeUnit.SECONDS.sleep(5);
    }
}

运行结果:

当一个线程调用 interrupt() 时,它并不会立刻中断,而是设置了这个线程的中断标志,线程具体什么时候中断由线程自己决定。利用这个特性,其实我们也可以实现退出标志那样的效果。

while(){...} 中我们可以调用 Thread.currentThread().isInterrupted() 或者 Thread.interrupted() 来判断线程是否已中断,如果中断了则退出线程,否则继续运行。

public class MyThread extends Thread {
    @Override
    public void run() {
        int i = 1;
        while (true) {
            if (this.isInterrupted()) {
                System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 线程中断,退出");
                break;
            }

            System.out.println("当前运行线程为:" +Thread.currentThread().getName() + " - 线程运行中,i =" + i++);
        }
    }
}

Thread.currentThread().isInterrupted()Thread.interrupted()两者都会返回中断标识,但是 Thread.interrupted() 会清理清除中断状态,而 Thread.currentThread().isInterrupted() 则不会清理中断状态。对于是否清理中断状态则根据我们的业务需求来做判断,如果我们希望我们后面的业务代码不受中断状态的影响而继续执行任务,这里推荐使用 Thread.interrupted() 清理中断状态。

使用 stop() 强制退出

一般不推荐采用这种方案,因为stop() 会强制终止一个线程,目前已被废弃。使用它会产生一些很严重的问题:

  1. 不安全和不可控制的终止stop() 会立刻强制终止线程的运行,无论该线程是否在执行关键任务,是否持有锁,是否是在进行资源的清理工作,通通强制终止,这就会导致线程可能处于不一致的状态,如果资源在清理,你终止就可能会导致资源泄露。
  2. 无法释放锁资源:如果线程持有锁,你将其终止了,锁资源得不到正确的释放,其他线程就会获取不到锁,甚至会产生死锁。
  3. 不可靠的异常抛出:调用stop()时,线程会立即抛出一个ThreadDeathThreadDeath 是一个 Error,一般来说我们都不会显示捕获它。

所以 stop() 是一个不安全、不可控制、容易引发问题的方法,在应用程序中不推荐。

阅读全文