Java并发编程实战 07 | 如何正确停止线程

news/2024/9/17 8:53:21/ 标签: java, 开发语言

什么时候需要停止一个线程?

一个线程被创建并启动之后,大部分情况下都会自然运行至结束,但是也有一些情况需要主动停止线程,比如:

  1. 用户主动取消执行:用户可能会中止一个正在进行的操作,这时需要停止相关线程。
  2. 运行时错误或超时:线程可能因为运行时错误或超时而需要被停止,以避免长时间占用资源。
  3. 服务关闭:当服务即将关闭时,可能需要停止所有正在运行的线程,以释放资源并确保干净地关闭服务。

所有这些情况都需要主动停止线程,但是要安全可靠地停止线程并不是一件容易的事情。

为什么不能强制停止线程?

实际上,当我们停止一个线程时,通常希望它至少能完成一些必要的收尾工作,如保存数据、切换状态等,而不是立即停止,以免导致状态混乱。

生活中,我们经常会遇到类似的情况。例如,当你将文件从电脑剪切并粘贴到U盘时,如果在传输过程中突然中断,你将面临一个问题:部分文件已经被复制到U盘,而另一部分还留在电脑上。这种情况下,你需要恢复到原始状态,避免出现一半的文件在U盘上,而另一半还在电脑里。

因此在处理文件传输或其他重要操作时,线程停止时我们需要确保所有操作都能完整地执行完毕,避免出现不完整或数据不一致的状态。

如何正确停止一个线程?

Java 语言本身并没有提供一种机制可以保证线程立即正确停止,但是它提供了 interrupt() 方法,这是一种协作机制,可以用于停止线程。

interrupt() 方法并不会直接终止线程,而是设置线程的中断状态。在线程中应该定期检查它的中断状态,并响应这个中断请求。被中断的线程拥有决定权,即它可以选择何时停止运行,甚至可以选择忽略这个中断请求。换句话说,如果一个线程不愿意响应中断请求,那么我们除了等待它完成任务或强制终止整个进程之外,别无他法。

如果被中断的线程正在等待、睡眠或被阻塞(例如,在调用 Thread.sleep() 或 wait() 等方法时),会立即抛出 InterruptedException 异常。线程可以捕获这个异常,并在异常处理程序中做出适当的响应(如清理资源、记录日志或退出线程)。

代码实践

使用stop()停止线程

使用stop()方法终止线程执行将导致线程立即停止,可能会引发意想不到的问题。

java">public class StopThread implements Runnable {@Overridepublic void run() {System.out.println("Start moving...");for (int i = 1; i <= 5; i++) {//Simulation of time required to moveint j = 50000;while (j > 0) {j--;}System.out.println(i + " batches have been moved");}System.out.println("End of moving");}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new StopThread());thread.start();// Try to stop it later.Thread.sleep(2);thread.stop();}
}//输出:
Start moving...
1 batches have been moved
2 batches have been moved
3 batches have been moved

可以看到,使用 stop 方法强制结束线程可能会导致操作不完全:上面的例子中,只有三批物品被移动,而这些物品在停止后没有被移回原处,这种情况可能带来数据不一致的问题。

由于这种强制停止线程的方式可能导致不稳定和无法预料的结果,stop 方法已经被官方弃用,并在源代码中标记为过时。出于安全考虑,建议使用其他更安全的方式来管理线程的中断和终止。

使用interrupt方法,线程不停止

在主线程中调用 interrupt 方法来中断目标线程时,目标线程可能无法感知到中断标志,也就是说,即使主线程发出了中断请求,目标线程可能继续运行,不会及时停止或做出其他响应。这种情况可能会导致线程无法按照预期停止,从而影响系统的稳定性和性能。

java">public class InterruptThreadWithoutFlag implements Runnable {@Overridepublic void run() {System.out.println("Start moving...");for (int i = 1; i <= 5; i++) {//Simulation of time required to moveint j = 50000;while (j > 0) {j--;}System.out.println(i + " batches have been moved");}System.out.println("End of moving");}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new StopThread());thread.start();// a little laterThread.sleep(2);thread.interrupt();}
}//输出:
Start moving...
1 batches have been moved
2 batches have been moved
3 batches have been moved
4 batches have been moved
5 batches have been moved
End of moving

你可能会发现,调用 interrupt 方法对线程没有任何效果。我们本来希望通过中断来停止线程,但它似乎完全忽视了我们的请求。

正如前面所提到的,是否响应中断信号取决于线程自身。为了确保线程能够响应中断,我们需要修改线程的逻辑,使其能够处理中断请求。这样,线程才能在接收到中断信号后及时停止。

使用interrupt,线程识别中断标志

当一个线程被中断时,线程内部可以通过调用 Thread.currentThread().isInterrupted() 方法来检查中断状态。如果该方法返回 true,则表示线程已经收到中断信号,线程可以据此执行相应的中断处理逻辑。

java">public class InterruptThread implements Runnable {@Overridepublic void run() {System.out.println("Start moving...");for (int i = 1; i <= 5; i++) {if(Thread.currentThread().isInterrupted()) {//Do some finishing work.break;}//Simulation of time required to moveint j = 50000;while (j > 0) {j--;}System.out.println(i + " batches have been moved");}System.out.println("End of moving");}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new InterruptThread());thread.start();Thread.sleep(2);thread.interrupt();}
}//输出:
Start moving...
1 batches have been moved
End of moving

查看打印输出后,我们可以发现,interrupt() 方法中断线程已经生效了。

中断某个线程时,该线程处于休眠状态

如果在线程处理中调用了 sleep 方法,即使线程未显式检查中断标志,它也会响应中断信号。例如,我们可以使用 Thread.sleep(1) 模拟每次搬运操作的时间,在主线程中等待 3 毫秒后进行中断,因此预计在搬运 2 到 3 批物品后线程会被中断,代码如下:

java">public class InterruptWithSleep implements Runnable {@Overridepublic void run() {System.out.println("Start moving...");for (int i = 1; i <= 5; i++) {// 模拟移动所需的时间try {Thread.sleep(1);System.out.println(i + " batches have been moved");} catch (InterruptedException e) {System.out.println(e.getMessage());break;}}System.out.println("End of moving");}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new InterruptWithSleep());thread.start();// 稍等片刻Thread.sleep(3);thread.interrupt();}
}//输出:
Start moving...
1 batches have been moved
2 batches have been moved
sleep interrupted
End of moving

这里还输出了 “sleep interrupted”,这是因为发生了中断异常。使程序执行到了 catch (InterruptedException e) 语句块,通过 e.getMessage() 输出了这个信息。

为什么会抛出异常呢?

这是因为当线程处于睡眠状态时,如果接收到中断信号,线程会立即响应这个中断。而响应中断的方式很特别,就是抛出一个异常:java.lang.InterruptedException: sleep interrupted。这确保了线程在睡眠时能够快速地对中断请求做出反应。

因此,当程序中有使用 sleep 或其他可能阻塞线程的方法(如 wait、join 等)时,如果这些方法可能会被中断,就需要特别注意 InterruptedException 异常的处理。我们可以在 catch 块中捕获这个异常,这样当线程进入阻塞状态时,仍然能够响应中断并执行相应的处理逻辑。

当sleep方法与isInterrupted一起使用时会发生什么情况?

大家有没有注意到,在前面的代码中,在捕获异常之后的处理中,我们使用了break主动结束这个循环。那么,我们能不能不用break,而是在循环入口处使用isInterrupted()判断?这样看起来更自然一些。让我们尝试一下:

java">public class SleepWithIsInterrupted implements Runnable {@Overridepublic void run() {System.out.println("Start moving...");for (int i = 1; i <= 5; i++) {if(Thread.currentThread().isInterrupted()) {//Do some finishing work.break;}//Simulation of time required to movetry {Thread.sleep(2);System.out.println(i + " batches have been moved");} catch (InterruptedException e) {System.out.println(e.getMessage());}}System.out.println("End of moving");}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new SleepWithIsInterrupted());thread.start();// a little laterThread.sleep(1);thread.interrupt();}
}//输出:
Start moving...
sleep interrupted
2 batches have been moved
3 batches have been moved
4 batches have been moved
5 batches have been moved
End of moving

输出结果有点出乎意料?中断之后怎么还继续移动第四批和第五批物品呢?

原因是一旦sleep()响应中断,就会清除线程的中断状态标志位,所以上面代码中的循环条件检查中,Thread.currentThread().isInterrupted()的结果一直是false,导致程序无法退出。

一般情况下,在实际业务代码中,主逻辑通常比较复杂,所以不建议在try-catch这里直接处理这个中断异常,而是直接将异常向上抛出,交由更高层次处理,便于功能分解和团队协作。你可以把把中断的后续收尾处理封装成一个方法,如下:

java">public class SleepSplitCase implements Runnable {@Overridepublic void run() {try {move();} catch (InterruptedException e) {System.out.println(e.getMessage());goBack();}}private void move() throws InterruptedException {System.out.println("Start moving...");for (int i = 1; i <= 5; i++) {//Simulation of time required to moveThread.sleep(2);System.out.println(i + " batches have been moved");}System.out.println("End of moving");}private void goBack() {// do some finishing work.}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new SleepSplitCase());thread.start();// a little laterThread.sleep(1);thread.interrupt();}
}//输出:
Start moving...
sleep interrupted

重新中断

有没有办法让 goBack 方法在 catch 块之外处理?

如前所述,当中断发生并抛出 InterruptedException 时,isInterrupted 的结果会被重置为 false。不过,可以通过再次调用 interrupt 方法来重新设置中断标志,这样 isInterrupted 的结果会变为 true。

基于这个前提,run 方法可以改为如下形式:

java">@Override
public void run() {try {move();} catch (InterruptedException e) {System.out.println(e.getMessage());Thread.currentThread().interrupt();}if (Thread.currentThread().isInterrupted()) {goBack();}
}

这样就避免了在catch代码块中处理业务逻辑!

确定是否发生中断的方法

  • boolean isInterrupted():用于检查当前线程是否被中断。这个方法会检查线程的中断状态,但不会清除中断标志。
  • static boolean interrupted():用于检查当前线程是否被中断。调用此方法后,它会将当前线程的中断标志重置为 false,即清除中断标志。

注意,interrupted() 方法针对的是当前线程,从源代码中可以很容易地看到:

我们来看下面的例子。我添加了一些注释来帮助理解:

java">public class CheckInterrupt {public static void main(String[] args) throws InterruptedException {Thread subThread = new Thread(() -> {for (; ; ) {}});subThread.start();subThread.interrupt();//获取中断标志System.out.println("isInterrupted: " + subThread.isInterrupted());//获取中断标志并重置// 虽然interrupted()是subThread线程调用的,但实际执行的是当前线程)  System.out.println("isInterrupted: " + subThread.interrupted());//中断当前线程 Thread.currentThread().interrupt();System.out.println("isInterrupted: " + subThread.interrupted());// Thread.interrupted() 与 subThread.interrupted() 的效果是相同的。System.out.println("isInterrupted: " + Thread.interrupted());}
}// 输出:
isInterrupted: true
isInterrupted: false
isInterrupted: true
isInterrupted: false

因为interrupted()会重置中断标志,所以最后的输出结果为false。

Jdk中响应中断信号的方法列表

JDK 有一系列内置方法可以响应中断信号 这些方法主要包括以下几种,它们会响应中断并抛出 InterruptedException:

java">Object.wait() / wait(long) / wait(long, int)
Thread.sleep(long) / sleep(long, int)
Thread.join() / join( long) / join(long, int)
java.util.concurrent.BlockingQueue.take() / put (E)
java.util.concurrent.locks.Lock.lockInterruptibly()
java.util.concurrent.CountDownLatch.await
java.util.concurrent.CyclicBarrier.await
java.util.concurrent.Exchanger.exchange(v)
Related methods of java.nio.channels.InterruptibleChannel
Related methods of java.nio.channels.Selector

http://www.ppmy.cn/news/1522845.html

相关文章

python---爬取QQ音乐

如Cookie为非vip&#xff0c;仅能获取非vip歌曲 1.下载包 pip install jsonpath 2.代码 import os import time import requests from jsonpath import jsonpathdef search_and_download_qq_music(query_text):headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; …

网络层ip协议

一.概念 ip协议主要是为了在复杂的网络环境中确定一个合适的路径来传输主机间的数据。简单来说就是用来确定主机的位置。 ip协议中的一些设备如下&#xff1a; 主机: 配有 IP 地址, 但是不进行路由控制的设备;路由器: 即配有 IP 地址, 又能进行路由控制;节点: 主机和路由器的统…

vue多环境配置和打包

件名的后缀来指定它们仅在特定模式下被加载。 .env&#xff1a;所有环境下都会加载的通用配置。 .env.local&#xff1a;本地覆盖配置&#xff0c;不加入版本控制。 .env.[mode]&#xff1a;仅为指定的模式加载的配置文件&#xff0c;例如.env.development、.env.production、…

Claude Enterprise推出计划

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

探究零工市场小程序如何改变传统兼职模式

近年来&#xff0c;零工市场小程序正逐渐改变传统的兼职模式&#xff0c;为求职者和雇主提供了一个更为高效、便捷的平台。本文将深入探讨零工市场小程序如何影响传统兼职模式&#xff0c;以及它带来的优势和挑战。 一、背景与挑战 传统的兼职市场往往存在信息不对称的问题&am…

本地Gitblit使用

首先创建一个本地的gitblit的服务&#xff0c;创建流程如下&#xff1a; 【GitBlit】Windows搭建Git服务器详细教程_搭建gitblit服务-CSDN博客 GitBlit的使用教程-CSDN博客 创建好一个仓库后&#xff0c;分配好用户权限&#xff0c;再将项目拉下来&#xff0c;这里是再visua…

C++如何创建一个单例模式的类

单例模式&#xff08;Singleton Pattern&#xff09;是一种创建型设计模式&#xff0c;其核心目的是确保一个类只有一个实例&#xff0c;并提供一个全局访问点来获取这个实例。单例模式通常用于管理共享资源&#xff0c;如配置文件、线程池、缓存等。在C中&#xff0c;创建一个…

页面小组件-表格封装(基础版)

说明 这一版封装的主要原因是当时有很多报表&#xff0c;数据列很多很多&#xff0c;基本在二十个左右。相应领导安排简洁代码&#xff0c;也为了偷懒&#xff0c;直接插槽循环搞起。其余是为了统一样式修改。 组件源码 <template><el-tablev-loading"loading…

DML、DQL、DCL的基础介绍

1.DML、DQL、DCL 1.1DML-介绍 DML英文全称Data Manipulation Language&#xff08;数据操作语言&#xff09;&#xff0c;用来对数据库中表的数据记录进行增删改操作 添加数据&#xff08;INSERT&#xff09;修改数据&#xff08;UPDATE&#xff09;删除数据&#xff08;DEL…

docker 启动容器报错 Error response from daemon: network XXX not found

华为的云服务器,重启后启动容器报上述错误 可能是容器依赖的网络被删除了 查看现有网络 docker network ls docker run -it \-p 80:80 \-p 443:443 \--name nginx \--network app --hostname nginx \-e TZAsia/Shanghai \--add-host adminJar:192.168.0.169 \-v /guazai/do…

比特币客户端和API

1. 比特比客户端的安装 Bitcoin Core 客户端适用于从 x86 Windows 到 ARM Linux 的不同架构和平台&#xff0c;如下图所示&#xff1a; 2. Bitcoin Core客户端的类型 2.1 Bitcoind Bitcoind 末尾的字母 d 表示 daemon (守护程序&#xff09;。所谓守护程序&#xff0c;就是指常…

线程相关内容

线程 一、介绍二、thread库1、构造函数&#xff08;1&#xff09;函数&#xff08;2&#xff09;说明&#xff08;3&#xff09;注意 2、join函数3、detach4、joinable函数5、get_id函数 三、mutex的种类1、mutex&#xff08;1&#xff09;介绍&#xff08;2&#xff09;lock&a…

从JavaScript入门Go三

前情提要 上一章中我们讲了Go中的变量与函数&#xff0c;这一节我们说说Go中的逻辑语法for、if、switch。最近正好有空&#xff0c;正好给大家更新一下入门的第三章。 PS&#xff1a;没看过的第一章、第二章的小伙伴&#xff0c;可以进入下面的链接查看 从JavaScript入门Go一 从…

损坏SD数据恢复的8种有效方法

SD卡被用于许多不同的产品来存储重要数据&#xff0c;如图片和重要的商业文件。如果您的SD卡坏了&#xff0c;您需要SD数据恢复来获取您的信息。通过从损坏的SD卡中取回数据&#xff0c;您可以确保重要文件不会永远丢失&#xff0c;这对于工作或个人原因是非常重要的。 有许多…

比特币网络和支付

1. 比特币网络 比特币网络是一个去中心化的点对点网络&#xff0c;节点之间可以直接进行交易。网络上有不同类型的节点。 1.1 比特币网络的节点 比特币网络的节点有两种主要类型&#xff1a;全节点也称为完整节点和简单支付验证&#xff08;Simple Payment Verification,SPV)节…

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个…

python画图|水平直方图绘制

前序学习过程中&#xff0c;我们一起研究了&#xff1a; 【a】直方图绘制基础教程&#xff1a;python画图|直方图绘制教程-CSDN博客 【b】 直方图绘制进阶教程&#xff1a;python画图|直方图绘制教程进阶-CSDN博客 【c】 堆叠直方图绘制教程&#xff1a;python画图|堆叠直方…

【JavaEE初阶】多线程(3)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 线程状态 线程安全 代码示例 解释 总结原因 解决方案-->加锁 t1和t2都加锁 且 同一个锁对象 t1和t2中只有一个加锁了 t1和t2都加锁,但锁对象不同 加锁 与线程等待…

35天学习小结

距离上次纪念日&#xff0c;已经过去了35天咯 算算也有5周了&#xff0c;在这一个月里&#xff0c;收获的也挺多&#xff0c;在这个过程中认识的大佬也是越来越多了hh 学到的东西&#xff0c;其实也没有很多&#xff0c;这个暑假多多少少还是有遗憾的~ 第一周 学习了一些有…

图像处理与编辑软件Adobe Photoshop(PS)2024WIN/MAC下载及安装教程

目录 一、软件概述 1.1 Photoshop 简介 1.2 主要功能 二、下载与安装 2.1 下载 2.2 安装步骤 2.3 注意事项 三、系统要求 3.1 硬件要求 3.2 操作系统 四、操作指南 4.1 基础操作 4.2 进阶技巧 4.3 高效工作 一、软件概述 1.1 Photoshop 简介 Adobe Photoshop&a…