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

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

[[383078]]

使用stop方法

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

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

使用标志位

  1. public class RunTask { 
  2.  
  3.     private volatile boolean stopFlag; 
  4.     private Thread taskThread; 
  5.  
  6.     public void start() { 
  7.         taskThread = new Thread(() -> { 
  8.             while (!stopFlag) { 
  9.                 System.out.println("doSomething"); 
  10.             } 
  11.         }); 
  12.         taskThread.start(); 
  13.     } 
  14.  
  15.     public void stop() { 
  16.         stopFlag = true
  17.     } 

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

使用interrupt方法

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

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

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

 

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

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

  1. public class RunTaskCase1 { 
  2.  
  3.     private Thread taskThread; 
  4.  
  5.     public void start() { 
  6.         taskThread = new Thread(() -> { 
  7.             while (true) { 
  8.                 System.out.println("doSomething"); 
  9.             } 
  10.         }); 
  11.         taskThread.start(); 
  12.     } 
  13.  
  14.     public void stop() { 
  15.         taskThread.interrupt(); 
  16.     } 

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

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

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

  1. isInterrupted
  2. interrupted

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

  1. @Test 
  2. public void testInterrupt() throws InterruptedException { 
  3.     Thread thread = new Thread(() -> { 
  4.         while (true) {} 
  5.     }); 
  6.     thread.start(); 
  7.     TimeUnit.MICROSECONDS.sleep(100); 
  8.     thread.interrupt(); 
  9.     // true 
  10.     System.out.println(thread.isInterrupted()); 
  11.     // true 
  12.     System.out.println(thread.isInterrupted()); 
  13.     // true 
  14.     System.out.println(thread.isInterrupted()); 
  1. @Test 
  2. public void testInterrupt2() { 
  3.     Thread.currentThread().interrupt(); 
  4.     // true 
  5.     System.out.println(Thread.interrupted()); 
  6.     // false 
  7.     System.out.println(Thread.interrupted()); 
  8.     // false 
  9.     System.out.println(Thread.interrupted()); 

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

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

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

  1. public class RunTaskCase2 { 
  2.  
  3.     private Thread taskThread; 
  4.  
  5.     public void start() { 
  6.         taskThread = new Thread(() -> { 
  7.             while (!Thread.currentThread().isInterrupted()) { 
  8.                 System.out.println("doSomething"); 
  9.             } 
  10.         }); 
  11.         taskThread.start(); 
  12.     } 
  13.  
  14.     public void stop() { 
  15.         taskThread.interrupt(); 
  16.     } 

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

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

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

  1. public class RunTaskCase3 { 
  2.  
  3.     private volatile boolean stopFlag; 
  4.     private Thread taskThread; 
  5.  
  6.     public void start() { 
  7.         taskThread = new Thread(() -> { 
  8.             while (stopFlag) { 
  9.                 try { 
  10.                     System.out.println("doSomething"); 
  11.                     TimeUnit.MICROSECONDS.sleep(100); 
  12.                 } catch (InterruptedException e) { 
  13.                     e.printStackTrace(); 
  14.                 } 
  15.             } 
  16.         }); 
  17.         taskThread.start(); 
  18.     } 
  19.  
  20.     public void stop() { 
  21.         stopFlag = true
  22.         taskThread.interrupt(); 
  23.     } 

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

  1. public class RunTaskCase4 { 
  2.  
  3.     private Thread taskThread; 
  4.  
  5.     public void start() { 
  6.         taskThread = new Thread(() -> { 
  7.             while (!Thread.currentThread().isInterrupted()) { 
  8.                 try { 
  9.                     System.out.println("doSomething"); 
  10.                     TimeUnit.MICROSECONDS.sleep(100); 
  11.                 } catch (InterruptedException e) { 
  12.                     // 重置中断标志位为true 
  13.                     Thread.currentThread().interrupt(); 
  14.                     e.printStackTrace(); 
  15.                 } 
  16.             } 
  17.         }); 
  18.         taskThread.start(); 
  19.     } 
  20.  
  21.     public void stop() { 
  22.         taskThread.interrupt(); 
  23.     } 

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

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

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

 

责任编辑:武晓燕 来源: Java识堂
相关推荐

2023-09-18 08:01:06

Spring管理Mybatis

2020-07-03 14:05:26

Serverless云服务商

2022-03-14 08:33:09

TypeScriptJavaScript前端

2021-11-29 18:27:12

Web Wasmjs

2019-09-23 13:37:09

Anthos谷歌Kubernetes

2011-09-20 15:51:42

NoSQL

2023-05-05 16:26:33

2011-10-27 13:37:51

网页设计

2019-09-23 13:10:02

容器进程

2019-01-18 15:01:17

云计算运维管理

2023-05-04 07:44:13

编程界小语言Java

2023-03-21 10:16:36

2021-02-25 14:09:55

人工智能数据机器学习

2018-01-23 11:48:17

Vue.js前端开发

2021-01-14 15:34:53

区块链比特币机器

2023-01-03 19:11:09

CPUI/O速度

2012-02-08 10:02:53

Web

2011-05-05 08:51:18

PHP

2023-07-19 08:00:00

Raft分布式系统

2023-05-17 16:37:29

点赞
收藏

51CTO技术栈公众号