如何利用中断操作优雅且正确的停止线程?

前言

嗨,这里是卷王~

我们知道启动一个线程是非常简单的,只需调用Thread类的start()方法即可,这样就可以执行run()方法中的任务代码了。但当我们需要停止一个正在执行的线程的时候?我们的代码该如何写呢?本文我们就一起看看如何正确的停止线程?

理解线程中断是什么?

我们可以理解中断就是线程的一个标识位属性(true/false),它代表的含义就是一个运行中的线程是否被其他线程进行了中断。但是中断只是线程之间的协作模式,仅仅通过设置中断标识并不能直接停止线程的执行。

当一个线程收到中断信号后,对于被停止的线程来说,它具体完全自主的权利,它即可以选择立即停止,也可以选择一段时间后再停止,也可以选择忽略这个中断请求。

为什么不强制停止?而是通知、协作呢,其实是为了数据安全!可以想象一下,如果不清楚这个线程的具体任务就贸然强制停止它,这必然会带来风险。比如一个正在运行的线程正在往一个文件里写入数据,如果贸然强制停止它,这就可能会造成数据安全问题。

如何利用中断标识停止线程?

理解了线程中断的概念,先来看看关于中断操作的方法:

  • public void interrupt()方法:中断线程。比如:当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标识位true并立即返回。设置标识仅仅是设置标识,线程A实际并没有被中断,它会继续往下执行。

源码如下:

  public void interrupt() {
         if (this != Thread.currentThread())
             checkAccess();
 ​
         synchronized (blockerLock) {
             Interruptible b = blocker;
             if (b != null) {
                 interrupt0();           // Just to set the interrupt flag 仅仅设置中断标识
                 b.interrupt(this);
                 return;
             }
         }
         interrupt0();
  }
复制代码
  • public boolean isInterrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false。

源码如下:

 public boolean isInterrupted() {
     // 传递false,则说明并不会清除中断标识
    return isInterrupted(false);
 }
复制代码
  • public static boolean interrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false。与isInterrupted()方法不同的是,该方法如果发现当前线程被中断,则会清除中断标识,并且该方法是静态方法,可以通过Thread类直接调用。从下面的源码可以知道,在isInterrupted()方法内部是获取当前当前调用线程的中断标识而不是调用isInterrupted()方法的实例对象的中断标识。

源码如下:

  public static boolean interrupted() {
      // 清除中断标识
     return currentThread().isInterrupted(true);
 }
复制代码

了解了中断相关方法后,我们下面看一个简单的根据中断标识去判断是否停止正在运行的线程demo。代码如下:

 /**
  * @BelongsProject: thoughtful-code
  * @BelongsPackage: top.msjava.thread.base
  * @Author: msJava
  * @CreateTime: 2022-08-07  07:39
  * @Description: 测试-中断正在运行中的线程
  * @Version: 1.0
  */
 public class ThreadStopTest implements Runnable {
 ​
     @Override
     public void run() {
         int count = 0;
         while (!Thread.currentThread().isInterrupted() && count < 1000) {
             System.out.println("count = " + count++);
         }
     }
 ​
     public static void main(String[] args) throws InterruptedException {
         // 启动子线程
         Thread thread = new Thread(new ThreadStopTest());
         thread.start();
 ​
         // 主线程休眠5毫秒
         Thread.sleep(5);
 ​
         System.out.println("main thread interrupt thread");
         // 对子线程进行中断操作
         thread.interrupt();
 ​
         // 等待子线程执行完毕
         thread.join();
         System.out.println("main over");
     }
 }
 ​
复制代码

在 ThreadStopTest类的 run() 方法中,首先判断线程是否被中断,然后再判断 count 值是否小于 1000。该线程的任务就是打印 0~999 的数字,每打印一个数字 count ++,可以看到,线程会在每次循环开始之前,检查是否被中断了。接下来在 main 函数中会启动该线程,然后休眠 5 毫秒后立刻中断线程,该线程会检测到中断信号,如果1000个数没有打印完线程就停止了,那就说明线程中断成功了。来看下后台输出:

 count = 0
 count = 1
 count = 2
 count = 3
 count = 4
 count = 5
 ... 省略 
 count = 269
 count = 270
 count = 271
 main thread interrupt thread
 count = 272
 main over
复制代码

上文我们测试了中断正在运行的线程,那么如果我们在线程处于sleep期间调用中断方法,该线程是否能够感应到呢?我们下面看一个简单的根据中断标识去判断是否停止正在sleep的线程demo。代码如下:

 /**
  * @BelongsProject: thoughtful-code
  * @BelongsPackage: top.msjava.thread.base
  * @Author: msJava
  * @CreateTime: 2022-08-07  07:39
  * @Description: 测试-中断睡眠状态中的线程
  * @Version: 1.0
  */
 public class ThreadSleepStopTest implements Runnable {
 ​
     @Override
     public void run() {
         try {
             System.out.println("thread begin sleep for 2000 seconds");
             Thread.sleep(2000000);
             System.out.println("thread awaking");
         } catch (InterruptedException e) {
             // 实际开发不建议捕捉到异常的catch没有进行任何处理逻辑,相当于把中断信号给隐藏了,这样做是非常不合理的,可以选择在方法签名中抛出异常。
             System.out.println("thread is interrupted while sleeping");
             return;
         }
         System.out.println("thread leaving normally");
     }
 ​
     public static void main(String[] args) throws InterruptedException {
         // 启动子线程
         Thread thread = new Thread(new ThreadSleepStopTest());
         thread.start();
         // 主线程休眠1000毫秒
         Thread.sleep(1000);
         // 对子线程进行中断操作
         thread.interrupt();
         // 等待子线程执行完毕
         System.out.println("main thread is over");
     }
 }
复制代码

后台输出:

 thread begin sleep for 2000 seconds
 main thread is over
 thread is interrupted while sleeping
复制代码

由上可知,如果 sleep、wait 等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。

总结

欧克~ 本文的内容到此结束啦,本文我们一起学习了,理解了线程中断是什么及中断相关方法,其次学习了如何正确的停止线程。总之,利用中断停止线程是目前Java中最合适的方式,因为请求中断线程,而不是强迫线程立即停止,因为这样可以避免数据错乱,也给线程有时间去结束当前的任务。

猜你喜欢

转载自juejin.im/post/7129060695504584711