每日一博 - 闲聊 Java 中的中断

文章目录

  • 概述
  • 常见的中断问题
    • 中断一个处于运行状态的线程
    • 中断一个正在 sleep 的线程
    • 中断一个由于获取 ReentrantLock 锁而被阻塞的线程
  • 如何正确地使用线程的中断标识
  • JDK 的线程池 ThreadPoolExecutor 内部是如何运用中断实现功能的
  • 小结

在这里插入图片描述


概述

在 Java 中,中断是一种线程协作方式 。

比如说,当线程 A 正在运行时,线程 B 可以通过中断线程 A,来指示线程 A 停止它正在执行的操作。但是线程 A 如何响应线程 B 的中断,是需要依靠线程 A 的代码处理逻辑来做决定的。


常见的中断问题

  • 首先,我们来看一下如何来中断一个线程,以及如何判断一个线程是否被中断了。

  • 接着,我们看下中断处于不同状态下的线程时,被中断的线程会做如何响应。 然后,我们学习如何正确地利用中断标识来处理中断

  • 最后,我们看一下 JDK 的线程池 ThreadPoolExecutor 内部是如何运用中断实现功能的

中断一个处于运行状态的线程

我们先来看下,如何中断一个线程,以及中断一个处于运行状态的线程后,这个线程会做出什么反应呢?

public class InterruptDemo {public static void main(String[] argc) throws InterruptedException {//1. 创建子线程Thread threadOne = new Thread(new Runnable() {@Overridepublic void run() {for (; ; ) {System.out.println("im threadOne thread:" + Thread.currentThread().isInterrupted());}}}, "THREAD-ONE");//2.启动线程threadOne.start();//3. main线程休眠1sThread.sleep(1000);//4. 中断子线程threadOne.interrupt();}
}

在这段代码中,我们首先创建了一个名为“THREAD-ONE”的线程。线程所做的事情很简单,就是打印一行文本。然后,我们启动这个线程。

接着,我们让 main 线程休眠 1s,这是为了让创建的子线程可以在被中断前,可以打印子线程的中断标识。
最后,我们调用子线程的 interrupt() 方法来中断子线程。

运行这段代码会发现,在代码 4 执行之前,子线程会一直输出:im threadOne thread:false。代码 4 执行完毕后,子线程会一直运行,并且一直输出:im threadOne thread:true

简单来说,我们可以通过调用线程的 interrupt() 方法来中断某个线程。中断处于运行状态的线程并不会对它造成影响,中断线程仅仅是把被中断的线程的标志设置为了 true。另外,我们可以调用线程的 isInterrupted() 方法,来判断线程是否被中断了


中断一个正在 sleep 的线程

中断处于运行状态的线程不会有影响,那中断一个正在 sleep 的线程,会对这个线程产生什么影响呢?我们再来看一个案例

public class InterruptSleepDemo {public static void main(String[] argc) throws InterruptedException {//1. 创建子线程Thread threadOne = new Thread(new Runnable() {@Overridepublic void run() {//1.1System.out.println("sub thread begin run");try {//1.2Thread.sleep(1000);} catch (InterruptedException e) {//1.3e.printStackTrace();}//1.4System.out.println("sub thread end run");}}, "THREAD-ONE");//2.启动线程threadOne.start();//3. main线程休眠100msThread.sleep(100);//4. 中断子线程threadOne.interrupt();System.out.println("threadOne already interrupted");}
}

我们先创建一个名称为“THREAD-ONE”线程,这个线程内部调用 Thread.sleep(1000) 来让自己休眠 1s,然后启动这个线程,开始运行。

接着,我们让 main 线程休眠 100ms,为的是在 main 线程执行代码 4 前,先让子线程执行代码 1.2 Thread.sleep(1000),并让子线程处于 TIMED_WAITING 状态。代码 4 则会调用子线程的 interrupt() 方法,来中断子线程。

运行上面这段代码,我们会看到这样的运行结果:

java.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at org.example.InterruptSleepDemo$1.run(InterruptSleepDemo.java:12)at java.lang.Thread.run(Thread.java:748)

可以看到,当 main 线程执行完代码 4,中断了子线程后,子线程会在代码 1.2 的地方抛出 InterruptedException 异常。然后,代码 1.3 会捕获到这个异常并打印异常信息,最后执行代码 1.4,并退出线程的执行,这时线程就处于终止状态了。

总的来说,中断一个由于调用 Thread.sleep() 方法而处于 TIMED_WAITING 状态的线程,会导致被中断的线程抛出 InterruptedException 异常


中断一个由于获取 ReentrantLock 锁而被阻塞的线程

当中断一个由于获取 ReentrantLock 锁而被阻塞的线程,会产生什么效果呢?我们来看一下这段代码示例:

public class InterruptLockDemo {//0.创建独占锁private final static ReentrantLock LOCK = new ReentrantLock();public static void main(String[] argc) throws InterruptedException {//1. 创建子线程Thread threadOne = new Thread(new Runnable() {@Overridepublic void run() {//1.1System.out.println("sub thread begin run");try {//1.2LOCK.lockInterruptibly();System.out.println("sub thread got lock");} catch (InterruptedException e) {//1.3e.printStackTrace();} finally {//1.4LOCK.unlock();}System.out.println("sub thread end run");}}, "THREAD-ONE");//2. main线程获取锁LOCK.lock();//3.启动线程threadOne.start();//4. main线程休眠100msThread.sleep(100);//5. 中断子线程threadOne.interrupt();//6. main线程释放锁LOCK.unlock();}
}

我们先创建一个独占锁,再创建一个名为“THREAD-ONE”的子线程,然后让 main 线程获取到这个独占锁,启动并运行子线程。让 main 线程休眠 100ms,是为了保证代码 1.2 的执行发生在中断子线程之前。

子线程执行到代码 1.2 时,发现锁已经被其他线程持有了,就会处于阻塞状态。当 main 线程执行到中断子线程代码时,子线程就会从阻塞状态返回,然后抛出 InterruptedException 异常。

运行上面这段代码,我们会得到如下所示的结果:

java.lang.InterruptedExceptionat java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)at org.example.InterruptLockDemo$1.run(InterruptLockDemo.java:18)at java.lang.Thread.run(Thread.java:748)
Exception in thread "THREAD-ONE" java.lang.IllegalMonitorStateExceptionat java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)at org.example.InterruptLockDemo$1.run(InterruptLockDemo.java:25)at java.lang.Thread.run(Thread.java:748)

综上所述,当中断一个由于使用 lockInterruptibly() 方法获取锁而阻塞的线程时,这个线程会从阻塞状态返回,然后会抛出 InterruptedException 异常


如何正确地使用线程的中断标识

说完这几种常见线程的中断案例,我们再来看一看,如何正确地使用线程的中断标识,来让被中断的线程正常退出执行呢

public class UseInterruptDemo {public static void main(String[] argc) throws InterruptedException {//1. 创建子线程Thread threadOne = new Thread(new Runnable() {@Overridepublic void run() {// 1.1for (; !Thread.currentThread().isInterrupted(); ) {System.out.println("---do something");}}}, "THREAD-ONE");//2.启动线程threadOne.start();//3. main线程休眠1sThread.sleep(1000);//4. 中断子线程threadOne.interrupt();System.out.println("threadOne already interrupted");}
}

在这段代码中,我们先创建一个线程,然后,启动并运行这个线程。让 main 线程休眠 1s,为的是让子线程的代码 1.1 可以打印数据,

接下来,调用子线程的 interrupt() 方法中断子线程。这时,子线程代码 1.1 的循环语句判断自己被中断了,就退出循环的执行,子线程也就结束运行了。

所以,中断处于运行状态的线程时,我们可以在被中断的线程内部判断当前线程的中断标识位是否被设置了,如果被设置了,就退出代码的执行,然后被中断的线程也就可以优雅地退出执行了


JDK 的线程池 ThreadPoolExecutor 内部是如何运用中断实现功能的

我们使用 ThreadPoolExecutor,在程序运行结束时,我们会调用它的 shutdown() 方法来关闭线程池。关闭线程池的其中一个步骤,就是中断当前不活跃的工作线程。

public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {...interruptIdleWorkers();...} finally {mainLock.unlock();}tryTerminate();}private void interruptIdleWorkers(boolean onlyOne) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers) {Thread t = w.thread;if (!t.isInterrupted() && w.tryLock()) {try {t.interrupt(); // 中断线程} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}} 

上面这段代码中,shutdown 方法内会调用 interruptIdleWorkers() 方法,来中断线程池中的空闲线程,interruptIdleWorkers() 内部会通过循环遍历所有的 Worker 线程,并且如果当前线程没被中断,则会中断当前线程。

那么,中断空闲线程会产生什么效果呢?我们需要看下线程池中工作线程的处理逻辑。

final void runWorker(Worker w) {...try {// 如果获取不到任务,则退出循环while (task != null || (task = getTask()) != null) {....}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}}private Runnable getTask() {...for (;;) {...if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}...try {//Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}}

这段代码,在 runWorker() 方法内,工作线程会通过循环从线程池队列里面获取任务,如果获取到任务,工作线程就进行处理;如果获取不到,就退出执行。

这个 getTask() 方法是负责从线程池队列里面获取任务的。默认情况下,getTask() 方法会执行 workQueue.take(),从队列里面获取任务。如果当前队列没有任务,这个方法会阻塞,也就是这个工作线程就会被阻塞。

当其他线程调用线程池的 shutDown() 方法时,会中断阻塞到 workQueue.take() 方法的工作线程,然后这个工作线程就会从阻塞中返回,并抛出 InterruptedException 异常

异常被捕获后,getTask() 方法继续执行 for 循环,接着发现线程池已经关闭了,getTask() 就会返回 null。到此为止,当前工作线程就执行完毕了,就会被释放掉


小结

在这里插入图片描述

Java 中每个线程都有一个中断标识,用来标识当前线程是否被中断了。我们可以通过调用线程的 interrupt() 方法来中断一个线程,一个线程被中断后,它的中断标识就被设置为了 true,我们可以通过调用线程的 isInterrupted() 方法来判断这个线程是否被中断。

当我们中断一个处于运行状态的线程,比如线程正在执行计算,这时仅仅是把线程的中断标识设置为了 true,并不会对计算任务造成影响。

还有一类线程,因为调用了 Object 类的 wait()、wait(long) 或 wait(long, int) 方法或者 Thread 的 join()、join(long)、join(long, int),sleep(long)、 sleep(long, int) 方法而被阻塞。当我们中断这类线程时,被阻塞的线程会从阻塞状态返回,并抛出 InterruptedException 异常。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/145277.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

应用在手机触摸屏中的电容式触摸芯片

触控屏(Touch panel)又称为触控面板,是个可接收触头等输入讯号的感应式液晶显示装置,当接触了屏幕上的图形按钮时,屏幕上的触觉反馈系统可根据预先编程的程式驱动各种连结装置,可用以取代机械式的按钮面板&…

ElementUI实现登录注册啊,axios全局配置,CORS跨域

一,项目搭建 认识ElementUI ElementUI是一个基于Vue.js 2.0的桌面端组件库,它提供了一套丰富的UI组件,包括表格、表单、弹框、按钮、菜单等常用组件,具备易用、美观、高效、灵活等优势,能够极大的提高Web应用的开发效…

Lua函数

--函数--无参无返回值 function F1()print("F1函数") end F1() print("*****************")--有参 function F2(a)print("F2函数"..a) end F2(2) --如果传入参数和函数数量不一致 --不会报错只是补空 F2(1,2) print("*****************&quo…

【夏虫语冰】测试服务器端口是否打开(命令行、Python)

文章目录 1、简介2、命令行2.1 telnet2.1.1 工具简介2.1.2 工具配置2.1.3 工具使用 2.2 curl2.2.1 工具简介2.2.1 工具下载2.2.1 工具使用 2.3 wget2.3.1 工具简介2.3.2 工具下载2.3.2 工具使用 2.4 nc2.4.1 工具简介2.4.2 工具安装2.4.3 工具使用 2.5 ssh2.5.1 工具简介2.5.2 …

数据链路层 MTU 对 IP 协议的影响

在介绍主要内容之前,我们先来了解一下数据链路层中的"以太网" 。 “以太网”不是一种具体的网络,而是一种技术标准;既包含了数据链路层的内容,也包含了一些物理层的内容。 下面我们再来了解一下以太网数据帧&#xff…

[Machine learning][Part3] numpy 矢量矩阵操作的基础知识

很久不接触数学了,machine learning需要用到一些数学知识,这里在重温一下相关的数学基础知识 矢量 矢量是有序的数字数组。在表示法中,矢量用小写粗体字母表示。矢量的元素都是相同的类型。例如,矢量不包含字符和数字。数组中元…

Android Jetpack组件架构:ViewModel的原理

Android Jetpack组件架构:ViewModel的原理 导言 本篇文章是关于介绍ViewModel的,由于ViewModel的使用还是挺简单的,这里就不再介绍其的基本应用,我们主要来分析ViewModel的原理。 ViewModel的生命周期 众所周知,一般…

字节一面:深拷贝浅拷贝的区别?如何实现一个深拷贝?

前言 最近博主在字节面试中遇到这样一个面试题,这个问题也是前端面试的高频问题,我们经常需要对后端返回的数据进行处理才能渲染到页面上,一般我们会讲数据进行拷贝,在副本对象里进行处理,以免玷污原始数据&#xff0c…

力扣 -- 10. 正则表达式匹配

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:bool isMatch(string s, string p) {int ms.size();int np.size();//处理后续映射关系s s;//处理后续映射关系p p;vector<vector<bool>> dp(m1,vector<bool>(n1));//初始化dp[0][0]true…

支付宝支付模块开发

生成二维码 使用Hutool工具类生成二维码 引入对应的依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.5</version> </dependency><dependency><groupId>com.go…

公司知识库搭建步骤,知识库建设与运营的四个步骤分享

在知识管理方面&#xff0c;团队中的每一员&#xff0c;都像是一名独行侠&#xff0c;自己的知识&#xff0c;满足自己的需要&#xff0c;这其中&#xff0c;就造成了很多无意义的精力消耗。 公司知识库搭建必要性 比如&#xff0c;一名员工撰写一QA文档&#xff0c;并没有将它…

国庆《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书行将售罄

国庆《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书行将售罄 国庆《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书行将售罄

【CUDA编程概念】一、什么是bank conflict?

前言 搜了不少答案&#xff0c;大多是在避免Bank Conflict&#xff0c;很难找到一个关于Bank Conflict的详细定义&#xff0c;这里找了些资料来尝试解释下&#xff1b; 一、基础概念 先简单复习下相关概念 GPU调度执行流程&#xff1a; SM调度单位为一个warp&#xff08;一…

Linux:修改mvn命令使用的maven路径

要在 Linux 上更改 Maven 的版本&#xff0c;需要调整 PATH 环境变量以指向所需版本的 Maven 安装目录。 打开终端或命令行界面。 使用文本编辑器打开 /etc/profile 文件&#xff1a; vi /etc/profile在文件的末尾添加以下行&#xff0c;将 PATH 环境变量指向新的 Maven 安装目…

4项简化IT服务台任务的ChatGPT功能

近几个月&#xff0c;随着人工智能聊天机器人 ChatGPT 风靡全球&#xff0c;用户可以通过它生成脚本、文章、运动计划表等。同时&#xff0c;这项技术在各行各业都能够进行无穷无尽的应用&#xff0c;在本文中&#xff0c;我们将探讨这项现代技术如何帮助ITSM团队提升服务交付和…

面试题六:Promise的使用,一文详细讲解

含义 Promise是异步编程的一种解决方案&#xff0c;比传统的解决方案&#xff08;回调函数和事件&#xff09;更合理更强大。 所谓Promise&#xff0c;简单说就是一个容器&#xff0c;里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说&#xff0c;P…

【JavaEE初阶】 计算机是如何工作的

文章目录 &#x1f332;计算机发展史&#x1f38b;冯诺依曼体系&#xff08;Von Neumann Architecture&#xff09;&#x1f38d;CPU 基本工作流程&#x1f4cc;逻辑门&#x1f388;电子开关 —— 机械继电器(Mechanical Relay)&#x1f388;门电路(Gate Circuit)NOT GATE&…

ElasticSearch深度分页解决方案

文章目录 概要ElasticSearch介绍es分页方法es分页性能对比表方案对比 From/Size参数深度分页问题Scroll#性能对比向前翻页 总结个人思考 概要 好久没更新文章了&#xff0c;最近研究了一下es的深分页解决方案。和大家分享一下&#xff0c;祝大家国庆节快乐。 ElasticSearch介…

windows下python开发环境的搭建 python入门系列 【环境搭建篇】

在正式学习Python之前要先搭建Python开发环境。由于Python是跨平台的&#xff0c;所以可以在多个操作系统上进行编程 一、python的下载安装与配置 1、Python解释器 1. 要进行Python开发&#xff0c;首先需要Python解释器&#xff0c;这里说的安装Python就是安装Python解释器…

利用mAP计算yolo精确度

当将yolo算法移植部署在嵌入式设备上&#xff0c;为了验证算法的准确率。将模型测试的结果保存为txt文件&#xff08;每一个txt文件&#xff0c;对应一个图片&#xff09;。此外&#xff0c;需要将数据集中的标签由[x,y,w,h]转为[x1,y1,x2,y2]。最后&#xff0c;运行验证代码 …