如何优雅地停止一个线程?
线程终止有两种情况:
- 线程的任务执行完成
- 线程在执行任务过程中发生异常
这两者属于线程自行终止,如何让线程 A 把线程 B 终止呢?
Java 中 Thread 类有一个 stop() 方法,可以终止线程,不过这个方法会让线程直接终止,在执行的任务立即终止,未执行的任务无法反馈,所以 stop() 方法已经不建议使用。
既然 stop() 方法如此粗暴,不建议使用,我们如何优雅地结束线程呢?
线程只有从 runnable 状态(可运行/运行状态) 才能进入terminated 状态(终止状态),如果线程处于 blocked、waiting、timed_waiting 状态(休眠状态),就需要通过 Thread 类的 interrupt() 方法,让线程从休眠状态进入 runnable 状态,从而结束线程。
当线程进入 runnable 状态之后,通过设置一个标识位,线程在合适的时机,检查该标识位,发现符合终止条件,自动退出 run () 方法,线程终止。
如我们模拟一个系统监控任务线程,代码如下
package constxiong.concurrency.a007; /** * 模拟系统监控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//测试系统监控器 } /** * 测试系统监控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //运行 10 秒后停止监控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("监控任务启动 10 秒后,停止..."); sm.stop(); } } /** * 系统监控器 * @author ConstXiong */ class SystemMonitor { private Thread t; /** * 启动一个线程监控系统 */ void start() { t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) {//判断当前线程是否被打断 System.out.println("正在监控系统..."); try { Thread.sleep(3 * 1000L);//执行 3 秒 System.out.println("任务执行 3 秒"); System.out.println("监控的系统正常!"); } catch (InterruptedException e) { System.out.println("任务执行被中断..."); } } }); t.start(); } void stop() { t.interrupt(); } }
执行结果
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
监控任务启动 10 秒后,停止...
任务执行被中断...
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
.
.
.
从代码和执行结果我们可以看出,系统监控器 start() 方法会创建一个线程执行监控系统的任务,每个任务查询系统情况需要 3 秒钟,在监控 10 秒钟后,主线程向监控器发出停止指令。
但是结果却不是我们期待的,10 秒后并没有终止了监控器,任务还在执行。
原因在于,t.interrupt() 方法让处在休眠状态的语句 Thread.sleep(3 * 1000L); 抛出异常,同时被捕获,此时 JVM 的异常处理会清除线程的中断状态,导致任务一直在执行。
处理办法是,在捕获异常后,继续重新设置中断状态,代码如下
package constxiong.concurrency.a007; /** * 模拟系统监控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//测试系统监控器 } /** * 测试系统监控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //运行 10 秒后停止监控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("监控任务启动 10 秒后,停止..."); sm.stop(); } } /** * 系统监控器 * @author ConstXiong */ class SystemMonitor { private Thread t; /** * 启动一个线程监控系统 */ void start() { t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) {//判断当前线程是否被打断 System.out.println("正在监控系统..."); try { Thread.sleep(3 * 1000L);//执行 3 秒 System.out.println("任务执行 3 秒"); System.out.println("监控的系统正常!"); } catch (InterruptedException e) { System.out.println("任务执行被中断..."); Thread.currentThread().interrupt();//重新设置线程为中断状态 } } }); t.start(); } void stop() { t.interrupt(); } }
执行结果如预期
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
监控任务启动 10 秒后,停止...
任务执行被中断...
到这里还没有结束,我们用 Thread.sleep(3 * 1000L); 去模拟任务的执行,在实际情况中,一般是调用其他服务的代码,如果出现其他异常情况没有成功设置线程的中断状态,线程将一直执行下去,显然风险很高。所以,需要用一个线程终止的标识来代替 Thread.currentThread().isInterrupted()。
修改代码如下
package constxiong.concurrency.a007; /** * 模拟系统监控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//测试系统监控器 } /** * 测试系统监控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //运行 10 秒后停止监控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("监控任务启动 10 秒后,停止..."); sm.stop(); } } /** * 系统监控器 * @author ConstXiong */ class SystemMonitor { private Thread t; private volatile boolean stop = false; /** * 启动一个线程监控系统 */ void start() { t = new Thread(() -> { while (!stop) {//判断当前线程是否被打断 System.out.println("正在监控系统..."); try { Thread.sleep(3 * 1000L);//执行 3 秒 System.out.println("任务执行 3 秒"); System.out.println("监控的系统正常!"); } catch (InterruptedException e) { System.out.println("任务执行被中断..."); Thread.currentThread().interrupt();//重新设置线程为中断状态 } } }); t.start(); } void stop() { stop = true; t.interrupt(); } }
执行结果
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
监控任务启动 10 秒后,停止...
任务执行被中断...
到这里基本算是优雅地让线程终止了。
- Java 面试题汇总PC端浏览【点这里】
- Java 面试题汇总小程序浏览,扫二维码
相关文章
- 线程通信(2)
- 如何查看子线程中的GC Alloc
- 还不知道如何在java中终止一个线程?快来,一文给你揭秘
- ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开
- C# 最基本的涉及模式(单例模式) C#种死锁:事务(进程 ID 112)与另一个进程被死锁在 锁 | 通信缓冲区 资源上,并且已被选作死锁牺牲品。请重新运行该事务,解决方案: C#关闭应用程序时如何关闭子线程 C#中 ThreadStart和ParameterizedThreadStart区别
- Python3 第三方线程池threadpool多参数和结果处理总结
- 线程的介绍(概念、作用)
- 如何保证多个线程同时启动?
- CMS gc实践总结(纠正并发线程数)
- Linux 有问必答:如何在 Linux 中统计一个进程的线程数
- ubuntu上如何查看当前使用的是哪种线程库
- 如何从线程返回信息——轮询、回调、Callable
- 线程池的原理及实现
- WebApi 数据保护操作未成功。这可能是由于未为当前线程的用户上下文加载用户配置文件导致的。当线程执行模拟时,可能会出现此情况。","ExceptionType":"System.Security.Cryptography.CryptographicException","StackTrace
- 还不知道如何在java中终止一个线程?快来,一文给你揭秘
- 浅析Collections.synchronizedList实现原理及如何做到线程安全、实现线程安全2种方式CopyOnWriteArrayList与Collections.synchronizedList的读写性能对比
- 线程池的核心参数和运行机制
- java中的线程如何理解——精简
- LoadTest内存和线程Troubleshooting实战
- JAVA优化篇 如何从茫茫日志中找到运行缓慢的线程
- Netty的线程模型
- 如何查看线程运行在哪个CPU上面
- Java线程同步与锁