当前位置:首页 > 人工智能

为什么说Volatile+Interrupt是停止线程优雅的姿势?

使用stop方法

调用stop方法,说V势会让正在运行的停止线程直接中止,有可能会让一些清理性的线程工作得不到完成。并且stop已经被标记为废弃的优雅方法,不建议使用。说V势

正确的停止使用姿势是使用两阶段终止的模式,即一个线程发送终止指令,线程另一个线程接收指令,优雅并且决定自己在何时停止。说V势

使用标志位

public class RunTask {      private volatile boolean stopFlag;     private Thread taskThread;     public void start() {          taskThread = new Thread(() -> {              while (!stopFlag) {                  System.out.println("doSomething");             }         });         taskThread.start();     }     public void stop() {          stopFlag = true;     } } 

「stopFlag上加volatile是停止保证可见性。我这个例子用了while循环不断判断,线程如果项目中用不到while的优雅话,可以在关键节点判断,说V势然后退出run方法即可」

使用interrupt方法

假如我们的停止任务中有阻塞的逻辑,如调用了Thread.sleep方法,线程如何让线程停止呢?

从线程状态转换图中寻找答案

从图中可以看到如果想让线程进入终止状态的前提是这个线程处于运行状态。当我们想要终止一个线程的站群服务器时候,如果此时线程处于阻塞状态,我们如何把它转换到运行状态呢?

我们可以通过调用Thread#interrupt方法,将阻塞状态的线程转换到就绪状态,进入由操作系统调度成运行状态,即可终止。

那线程在运行状态中调用interrupt方法,会发生什么呢?

public class RunTaskCase1 {      private Thread taskThread;     public void start() {          taskThread = new Thread(() -> {              while (true) {                  System.out.println("doSomething");             }         });         taskThread.start();     }     public void stop() {          taskThread.interrupt();     } } 

依次调用start方法和stop方法,发现线程并没有停止。

「其实当线程处于运行状态时,interrupt方法只是在当前线程打了一个停止的标记,停止的逻辑需要我们自己去实现」

「Thread类提供了如下2个方法来判断线程是否是中断状态」

isInterrupted interrupted

这2个方法虽然都能判断状态,但是有细微的差别

@Test public void testInterrupt() throws InterruptedException {      Thread thread = new Thread(() -> {          while (true) { }     });     thread.start();     TimeUnit.MICROSECONDS.sleep(100);     thread.interrupt();     // true     System.out.println(thread.isInterrupted());     // true     System.out.println(thread.isInterrupted());     // true     System.out.println(thread.isInterrupted()); }  @Test public void testInterrupt2() {      Thread.currentThread().interrupt();     // true     System.out.println(Thread.interrupted());     // false     System.out.println(Thread.interrupted());     // false     System.out.println(Thread.interrupted()); } 

「isInterrupted和interrupted的方法区别如下」

Thread#isInterrupted:测试线程是否是中断状态,执行后不更改状态标志 Thread#interrupted:测试线程是否是中断状态,执行后将中断标志更改为false

「所以此时我们不需要自已定义状态,亿华云计算直接用中断标志即可,之前的代码可以改为如下」

public class RunTaskCase2 {      private Thread taskThread;     public void start() {          taskThread = new Thread(() -> {              while (!Thread.currentThread().isInterrupted()) {                  System.out.println("doSomething");             }         });         taskThread.start();     }     public void stop() {          taskThread.interrupt();     } } 

当线程处于阻塞状态时,调用interrupt方法,会抛出InterruptedException,也能终止线程的执行

「注意:发生异常时线程的中断标志为会由true更改为false。」

所以我们有如下实现 当线程处于运行状态:用自己定义的标志位来退出 当线程处于阻塞状态:用抛异常的方式来退出

public class RunTaskCase3 {      private volatile boolean stopFlag;     private Thread taskThread;     public void start() {          taskThread = new Thread(() -> {              while (stopFlag) {                  try {                      System.out.println("doSomething");                     TimeUnit.MICROSECONDS.sleep(100);                 } catch (InterruptedException e) {                      e.printStackTrace();                 }             }         });         taskThread.start();     }     public void stop() {          stopFlag = true;         taskThread.interrupt();     } } 

当然也可以一直用中断标志来退出,「注意,当发生异常的时候需要重置中断标志位」。

public class RunTaskCase4 {      private Thread taskThread;     public void start() {          taskThread = new Thread(() -> {              while (!Thread.currentThread().isInterrupted()) {                  try {                      System.out.println("doSomething");                     TimeUnit.MICROSECONDS.sleep(100);                 } catch (InterruptedException e) {                      // 重置中断标志位为true                     Thread.currentThread().interrupt();                     e.printStackTrace();                 }             }         });         taskThread.start();     }     public void stop() {          taskThread.interrupt();     } } 

最后问大家一个问题?RunTaskCase3和RunTaskCase4哪种实现方式比较好呢?

「虽然RunTaskCase4代码看起来更简洁,但是RunTaskCase4不建议使用,因为如果在run方法中调用了第三方类库,发生了InterruptedException异常,但是没有重置中断标志位,会导致线程一直运行下去,同理RunTaskCase2也不建议使用」。

本文转载自微信公众号「 Java识堂」,可以通过以下二维码关注。转载本文请联系 Java识堂公众号。

分享到:

滇ICP备2023006006号-16