在开发中,经常会遇到需要停止一个正在运行的线程的场景,以前的做法是通过Thread.stop() 的方式来停止具体的线程,但是这个方法目前是被废弃掉的,不推荐使用。不推荐使用的原因如下:

1、该方式是通过立即抛出ThreadDeath异常来达到停止线程的目的,而且此异常抛出可能发生在程序的任何一个地方,包括catch、finally等语句块中。

2、由于抛出ThreadDeatch异常,会导致该线程释放所持有的所有的锁,而且这种释放的时间点是不可控制的,可能会导致出现线程安全问题和数据不一致情况,比如在同步代码块中在执行数据更新操作时线程被突然停止。

    因此,为了避免Thread.stop()带来的问题,推荐使用被称作为Interrupt(中断)的协作机制来停止一个正在运行的线程。在JVM中,每个线程都有一个与之关联的Boolean属性,被称之为中断状态,可以通过Thread.currentThread().isInterrupted()来获取当前线程的中断状态,初始值为false。中断状态仅仅是线程的一个属性,用以表明该线程是否被中断。因为中断是线程之间的一种协作机制,因此对于被中断的线程而言,可以对中断做出响应,也可以不做任何响应(这种做法被称之为“生吞中断”,通常不建议生吞中断)。举个例子,如下代码所示:

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("hello,world");
        }
    });
    t1.start();
    TimeUnit.MILLISECONDS.sleep(50);
    t1.interrupt();
}

    在线程 t1 中一直执行打印操作并不断的轮询当前线程的中断状态,来判断当前线程是否被中断。主线程等待50ms,然后通过调用线程 t1 的 interrupt() 方法来设置线程 t1 的中断状态为 true,线程 t1 在下次轮询中检测到自己被其他线程中断,进而跳出循环,结束线程执行。在上述代码中,如果把 !Thread.currentThread().isInterrupted() 替换为 true,也就是不在循环条件中判断线程的中断状态,那么线程 t1 会一直打印下去,不会停止,即使被自己中断状态被主线程设置为true,也就是说被中断线程可以对中断做出响应,也可以忽略中断。

    虽然在我们自己的程序中,我们可以决定要不要对中断进行响应处理,但是在Java中,有些方法已经实现了对中断的响应处理,比如Thread.sleep()、Object.wait()、BlockingQueue.put()、BlockingQueue.take()等等。当线程执行正在这些方法时,被其他线程中断掉,该线程会首先清除掉中断状态(设置中断属性为false),然后抛出InterruptedException异常。如下代码所示:

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            System.out.println("线程初始中断状态 -> " + Thread.currentThread().isInterrupted());
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            System.out.println("抛出异常后线程中断状态 -> " + Thread.currentThread().isInterrupted());
            e.printStackTrace();
        }
    });
    t1.start();
    TimeUnit.MILLISECONDS.sleep(50);
    t1.interrupt();
}

运行结果如下所示:

java 多线程并发卡死 java多线程停止_线程中断

    在平时代码中对中断的处理,不推荐生吞中断,通常有这两种做法:抛出中断异常、上传中断状态。对于抛出中断异常这种做法,也就是Thread.sleep()的这种做法。但是在某些情况下,抛出中断异常并不合适或者不能上抛异常,比如在Runnable定义的任务中,不能对外抛出异常,必须要在代码里处理掉异常。这种情况下,最好的做法就是上传中断状态,保留中断发生的证据,以便调用栈中更高层的代码能够知道发生了中断,并对中断做出响应。具体代码如下所示:

public class TaskRunner implements Runnable {
    @Override
    public void run() {
        try {
            //todo ...
            Thread.sleep(1000);
            //todo ...
        } catch (InterruptedException e) {
            e.printStackTrace();
            //中断状态在抛出异常前,被清除掉,因此在此处重置中断状态
            Thread.currentThread().interrupt();
        }
    }
}

相比于通过Thread.stop()来终止线程,采用中断的协同机制来停止线程的方式可以让程序自己来控制在合适的地方释放自己所持有的锁,并且在线程退出前完成相关的清理工作,当然程序中也可以选择忽略中断,继续运行。总之,相比Thread.stop()在任何时间任何地点都有可能立刻停止线程来说,采用中断这种方式把被中断线程的相关后续操作以及退出都交给了程序来控制处理,更加可控。

最后补充一下:Thread.interrupted() 与 Thread.currentThread().isInterrupted()的区别

Thread.interrupted() 的实现源码如下所示

java 多线程并发卡死 java多线程停止_多线程_02

Thread.currentThread().isInterrupted()的实现源码如下所示

java 多线程并发卡死 java多线程停止_停止线程_03

可以看得出两个都是调用了 Thread对象的 isInterrupted(boolean ClearInterrupted) 方法,该方法是个native方法。

java 多线程并发卡死 java多线程停止_java 多线程并发卡死_04

通过代码可以看得出,Thread.interrupted() 是获取并清除当前线程的中断状态,Thread.currentThread().isInterrupted() 只是获取当前线程的中断状态,并不清除当前线程的中断状态。