如何中断一个线程

为什么要线程中断

联想实际生活中,例如我们正在下载一个超大的文件,可能网速较慢导致任务时长太久。如果我不想等了怎么办?我可以选择中断正在执行的下载任务,等网络好了再下载。所以在程序开发中,中断任务也十分重要。如果线程的执行时间远远超过了我们预期的时长,我们就可以中断线程的执行。

如何中断线程的执行

在JDK中提供了一系列与中断线程相关的操作,例如stop、interrupt等。但实际上stop方法已经不再推荐使用,它是有系统强制终止线程,这种方式非常的不优雅,同时这种暴力终止线程的方式还会带来数据不一致等一些列问题,所以JDK中已经将其标记成启用,即不推荐在开发中使用。
而目前推荐的方式是通过调用interrupt向目标线程发送一个中断信号,目标线程根据中断信号来实现线程中断操作。在了解具体如何中断线程前我们需要先详细了解几个中断线程相关的方法。

相关方法

interrupt

该方法在JDK中的定义如下:

public void interrupt(){
  //省略具体实现
}

中断目标线程,给目标线程发送一个中断信号,线程被打上中断标记。

isInterrupted

该方法在JDK中的定义如下:

public boolean isInterrupted(){
  //省略具体实现
}

判断目标线程是否被中断,不会清除中断标记。

isInterrupted

private native boolean isInterrupted(boolean ClearInterrupted)

判断目标线程是否被中断,会清除中断标记。

线程中断案例

中断无效

@Slf4j
public class ThreadInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while (true){
                Thread.State state = Thread.currentThread().getState();
                boolean interrupted = Thread.currentThread().isInterrupted();
                log.info("线程正在运行中,当前线程状态[{}],线程中断状态[{}]",state,interrupted);
            }
        });
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}

上面代码会在1s后结束运行吗?答案是不会。我们先分析整个代码逻辑,首先创建线程t1,线程t1的逻辑就是一直打印线程的状态和中断标记信息。接着就是启动线程t1,t1线程启动后主线程睡眠1s之后中断线程t1。最后打印的内容如下所示:

线程正在运行中,当前线程状态[RUNNABLE],线程中断状态[true]

从打印日志上来说,线程一直处于运行中的状态,而线程的中断标志是「true」。但是实际效果是线程t1并未有任何影响,而是一直正常的运行。
Java中的线程中断,并不是直接让目标线程中断执行,只是将目标线程的中断标记修改成true。而我们线程中的业务代码并没有响应中断,所以目标线程一直处于执行状态。所以我们只需要简单的修改代码如下:

@Slf4j
public class ThreadInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while (true){
                Thread.State state = Thread.currentThread().getState();
                boolean interrupted = Thread.currentThread().isInterrupted();
                log.info("线程正在运行中,当前线程状态[{}],线程中断状态[{}]",state,interrupted);
                if (interrupted){
                    log.info("线程已中断,线程退出执行!");
                    break;
                }
            }
        });
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}

代码修改如下,我们在run方法的内部增加了部分判断逻辑。当线程中断标记为true时,我们立马退出run中的方法逻辑,从而达到了中断线程执行的目的。

线程状态对中断的影响

在上面代码中,线程是处于「RUNNABLE」状态,所以我们可以通过判断中断标记来中断线程。如果我们现在我们的线程不是在运行状态中,那要怎样去响应中断呢?

@Slf4j
public class ThreadInterrupt2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() ->{
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        Thread.sleep(1000);
        log.info("中断线程t1");
        t1.interrupt();
    }
}

上面的代码比较简单,创建线程t1然后让线程t1睡眠5秒,然后在主线程睡眠1s后执行中断线程t1。最后的打印结果如下:

17:00:51.092 [main] INFO com.buydeem.share.interrupt.ThreadInterrupt2 - 中断线程t1
java.lang.InterruptedException: sleep interrupted
...

线程t1调用sleep方法,此时线程的状态处于「TIME_WAITING」。从运行结果可以看出,如果线程处于「TIME_WAITING」时,目标线程响应中断的方式是通过抛出「InterruptedException」异常的。所以在实际开发中我们应该处理该异常,从而达到中断线程的目的。

@Slf4j
public class ThreadInterrupt2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() ->{
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                log.info("线程已被中断,结束线程业务逻辑");
                return;
            }
        });
        t1.start();
        Thread.sleep(1000);
        log.info("中断线程t1");
        t1.interrupt();
    }
}

修改后代码如下:

17:09:07.438 [main] INFO com.buydeem.share.interrupt.ThreadInterrupt2 – 中断线程t1
17:09:08.442 [Thread-0] INFO com.buydeem.share.interrupt.ThreadInterrupt2 – 线程已被中断,结束线程业务逻辑

上面分析的是线程处于「TIME_WAITING」状态时中断目标线程,目标线程会抛出「InterruptedException」来响应中断。如果线程此时处于「BLOCKED」时,中断线程会发生什么呢?

@Slf4j
public class ThreadInterrupt3 {
    public static void main(String[] args) throws InterruptedException {
        Object syncO = new Object();

        Thread t1 = new Thread(()->{
           synchronized (syncO){
               while (true){}
           }
        });

        Thread t2 = new Thread(()->{
            synchronized (syncO){
                while (true){}
            }
        });

        t1.start();
        t2.start();

        Thread.sleep(1000);

        log.info("t1:state = {} isInterrupted = {},t2:state = {} isInterrupted = {}",t1.getState(),t1.isInterrupted(),t2.getState(),t2.isInterrupted());

        t1.interrupt();
        t2.interrupt();

        log.info("t1:state = {} isInterrupted = {},t2:state = {} isInterrupted = {}",t1.getState(),t1.isInterrupted(),t2.getState(),t2.isInterrupted());
        
    }
}

上面代码的主要逻辑是,创建线程t1和t2两个线程,然后这两个线程根据同一对象进行线程同步,所以只会有一个线程获取到同步锁而处于「RUNNABLE」状态,而另一个线程处于「BLOCKED」状态。最后代码的运行结果如下:

t1:state = BLOCKED isInterrupted = false,t2:state = RUNNABLE isInterrupted = false
t1:state = BLOCKED isInterrupted = true,t2:state = RUNNABLE isInterrupted = true

从最后结果可以看出,处于「BLOCKED」状态的线程并未受到任何影响。

小结

从我们前面的示例和分析我们知道,Java中的线程中断并不是直接中断正在执行的线程,而是修改线程的中断标记。如果想要实现真正的中断线程,我们还需要在业务代码中响应中断信息,这样我们才能达到真正的线程中断效果。
而线程的中断还会受到线程状态的影响,不同状态的线程状态,他们响应中断的方式也不太一样。对于「WAITING和TIME_WAITING」而言,它们会在收到中断信息时抛出中断异常,而其他状态的线程并不会。
对于线程状态还不太清楚的,可以关注工作号查看之前的文章关于线程状态的介绍。


原文始发于微信公众号(一只菜鸟程序员):如何中断一个线程

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/73052.html

(0)

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!