朴实的聊聊很多人会误解/不懂的Java并发中断机制

简介: 朴实的聊聊很多人会误解/不懂的Java并发中断机制

| 好看请赞,养成习惯


  • 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想
  • If you can NOT explain it simply, you do NOT understand it well enough


横看成岭侧成峰,远近高低各不同,并发编程理论系列基本已经结束,相信大家有了理论的铺垫,近看源码才能发现其设计之美,不会一头雾水


本来是要介绍 AQS 作为我们走进并发编程源码环节的第一步,但 AQS 涉及的知识点也还真有点多,每一个都够单独拿出来说一说,恰巧有朋友私信我“不理解线程的中断机制”,中断机制又恰巧是 AQS API实现的一部分,更贯穿于整个并发编程内容中。于是就打算单独说一说这个小机制,先让大家做到心中有 number


在学习/编写并发程序时,总会听到/看到如下词汇:


  • 线程被中断或抛出InterruptedException


  • 设置了中断标识


  • 清空了中断标识


  • 判断线程是否被中断


在 Java Thread 类又提供了长相酷似,让人傻傻分不清的三个方法来处理并发中断问题:


  • interrupt()
  • interrupted()
  • isInterrupted()


微信图片_20220510175335.png


看到这我不禁会问自己:


微信图片_20220510175358.png


什么是中断机制?


微信图片_20220510175432.png


刚刚接触【中断】这个词时,先入为主的概念就是“直接中断/打断”正在做的事,使其停止。我的理解是这样的:


你:在打游戏

女朋友:别打游戏了,赶快过来吃饭

你:听到女朋友招呼之后立马中断手中的游戏乖乖过去吃饭


微信图片_20220510175457.png


在多线程编程中,中断是一种【协同】机制,怎么理解这么高大上的词呢?就是女朋友叫你吃饭,你收到了中断游戏通知,但是否马上放下手中的游戏去吃饭看你心情 。在程序中怎样演绎这个心情就看具体的业务逻辑了,Java 的中断机制就是这么简单

如果还没改变这个先入为主的概念,我怀你你没有女朋友, 我们拥抱一下


为什么会有中断机制?


中断是一种协同机制,我觉得就是解决【当局者迷】的状况


现实中,你努力忘我没有昼夜的工作,如果再没有人告知你中断,你身体是吃不消的。

在多线程的场景中,有的线程可能迷失在怪圈无法自拔(自旋浪费资源),这时就可以用其他线程在恰当的时机给它个中断通知,被“中断”的线程可以选择在恰当的时机选择跳出怪圈,最大化的利用资源


那程序中如何中断?怎样识别是否中断?又如何处理中断呢?这就与上文提到的三个方法有关了


interrupt() VS isInterrupted() VS interrupted()


Java 的每个线程对象里都有一个 boolean 类型的标识,代表是否有中断请求,可你寻遍 Thread 类你也不会找到这个标识,因为这是通过底层 native 方法实现的。


interrupt()


interrupt() 方法是 唯一一个 可以将上面提到中断标志设置为 true 的方法,从这里可以看出,这是一个 Thread 类 public 的对象方法,所以可以推断出任何线程对象都可以调用该方法,进一步说明就是可以一个线程 interrupt 其他线程,也可以 interrupt 自己。其中,中断标识的设置是通过 native 方法 interrupt0 完成的


微信图片_20220510175522.png


在 Java 中,线程被中断的反应是不一样的,脾气不好的直接就抛出了 InterruptedException()


微信图片_20220510175547.png


该方法注释上写的很清楚,当线程被阻塞在:


  1. wait()


  1. join()


  1. sleep()


这些方法时,如果被中断,就会抛出 InterruptedException 受检异常(也就是必须要求我们 catch 进行处理的)


熟悉 JUC 的朋友可能知道,其实被中断抛出 InterruptedException 的远远不止这几个方法,比如:


微信图片_20220510175609.png


反向推理,这些可能阻塞的方法如果声明有 throws InterruptedException , 也就暗示我们它们是可中断的


调用 interrput() 方法后,中断标识就被设置为 true 了,那我们怎么利用这个中断标识,来判断某个线程中断标识到底什么状态呢?


isInterrupted()


微信图片_20220510175633.png


这个方法名起的非常好,因为比较符合我们 bean boolean 类型字段的 get 方法规范,没错,该方法就是返回中断标识的结果:


  • true:线程被中断,


  • false:线程没被中断或被清空了中断标识(如何清空我们一会看)


拿到这个标识后,线程就可以判断这个标识来执行后续的逻辑了。有起名好的,也有起名不好的,就是下面这个方法:


interrupted()


按照常规翻译,过去时时态,这就是“被打断了/被打断的”,其实和上面的 isInterrupted() 方法差不多,两个方法都是调用 private 的 isInterrupted() 方法, 唯一差别就是会清空中断标识(这是从方法名中怎么也看不出来的)


微信图片_20220510175720.png


因为调用该方法,会返回当前中断标识,同时会清空中断标识,就有了那一段有点让人迷惑的方法注释:


微信图片_20220510175743.png


来段程序你就会明白上面注释的意思了:


Thread.currentThread().isInterrupted(); // true
Thread.interrupted() // true,返回true后清空了中断标识将其置为 false
Thread.currentThread().isInterrupted(); // false
Thread.interrupted() // false


这个方法总觉得很奇怪,现实中有什么用呢?


当你可能要被大量中断并且你想确保只处理一次中断时,就可以使用这个方法了


该方法在 JDK 源码中应用也非常多,比如(后续文章会具体分析,这里知道该方法的作用和使用场景就好):


微信图片_20220510175909.png


相信到这里你已经能明确分辨三胞胎都是谁,并发挥怎样的作用了,那么有哪些场景我们可以使用中断机制呢?


中断机制的使用场景


通常,中断的使用场景有以下几个


  • 点击某个桌面应用中的关闭按钮时(比如你关闭 IDEA,不保存数据直接中断好吗?);


  • 某个操作超过了一定的执行时间限制需要中止时;


  • 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;


  • 一组线程中的一个或多个出现错误导致整组都无法继续时;


因为中断是一种协同机制,提供了更优雅中断方式,也提供了更多的灵活性,所以当遇到如上场景等,我们就可以考虑使用中断机制了


使用中断机制有哪些注意事项


其实使用中断机制无非就是注意上面说的两项内容:


  1. 中断标识


  1. InterruptedException


前浪已经将其总结为两个通用原则,我们后浪直接站在肩膀上用就可以了,来看一下这两个原则是什么:


原则-1


如果遇到的是可中断的阻塞方法, 并抛出 InterruptedException,可以继续向方法调用栈的上层抛出该异常;如果检测到中断,则可清除中断状态并抛出 InterruptedException,使当前方法也成为一个可中断的方法


原则-2


若有时候不太方便在方法上抛出 InterruptedException,比如要实现的某个接口中的方法签名上没有 throws InterruptedException,这时就可以捕获可中断方法的 InterruptedException 并通过 Thread.currentThread.interrupt() 来重新设置中断状态。


再通过个例子来加深一下理解:


本意是当前线程被中断之后,退出while(true), 你觉得代码有问题吗?(先不要向下看)

Thread th = Thread.currentThread();
while(true) {
  if(th.isInterrupted()) {
    break;
  }
  // 省略业务代码
  try {
    Thread.sleep(100);
  }catch (InterruptedException e){
    e.printStackTrace();
  }
}


打开 Thread.sleep 方法:


微信图片_20220510180023.png


sleep 方法抛出 InterruptedException后,中断标识也被清空置为 false,我们在catch 没有通过调用 th.interrupt() 方法再次将中断标识置为 true,这就导致无限循环了

这两个原则很好理解。总的来说,我们应该留意 InterruptedException,当我们捕获到该异常时,绝不可以默默的吞掉它,什么也不做,因为这会导致上层调用栈什么信息也获取不到。其实在编写程序时,捕获的任何受检异常我们都不应该吞掉


JDK 中有哪些使用中断机制的地方呢?


中断机制贯穿整个并发编程中,这里只简单列觉大家经常会使用的,我们可以通过阅读JDK源码来进一步了解中断机制以及学习如何使用中断机制


ThreadPoolExecutor


ThreadPoolExecutor 中的 shutdownNow 方法会遍历线程池中的工作线程并调用线程的 interrupt 方法来中断线程


微信图片_20220510180046.png


微信图片_20220510180103.png


FutureTask


FutureTask 中的 cancel 方法,如果传入的参数为 true,它将会在正在运行异步任务的线程上调用 interrupt 方法,如果正在执行的异步任务中的代码没有对中断做出响应,那么 cancel 方法中的参数将不会起到什么效果


微信图片_20220510180139.png


总结


到这里你应该理解Java 并发编程中断机制的含义了,它是一种协同机制,和你先入为主的概念完全不一样。区分了三个相近方法,说明了使用场景以及使用原则,同时又给出JDK源码一些常见案例,相信你已经胸中有沟壑了,接下来,跟上节奏,我们陆续走进源码吧


灵魂追问


  1. 抛出 InterruptedException 后,中断标识就一定被清空吗?


  1. 处在死锁状态的线程是否可以被中断呢?


  1. 进入临界区的线程能否被中断呢?如果不能有什么办法能响应中断吗?


  1. 个人感觉interrupted这个方法名称不是特别好,如果你也觉得不好,让你设计这个地方,你有什么想法?
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
1月前
|
Java 编译器
探索Java中的异常处理机制
【10月更文挑战第35天】在Java的世界中,异常是程序运行过程中不可避免的一部分。本文将通过通俗易懂的语言和生动的比喻,带你了解Java中的异常处理机制,包括异常的类型、如何捕获和处理异常,以及如何在代码中有效地利用异常处理来提升程序的健壮性。让我们一起走进Java的异常世界,学习如何优雅地面对和解决问题吧!
|
16天前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
|
17天前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
42 2
|
17天前
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
39 2
|
20天前
|
Java 程序员 UED
深入理解Java中的异常处理机制
本文旨在揭示Java异常处理的奥秘,从基础概念到高级应用,逐步引导读者掌握如何优雅地管理程序中的错误。我们将探讨异常类型、捕获流程,以及如何在代码中有效利用try-catch语句。通过实例分析,我们将展示异常处理在提升代码质量方面的关键作用。
31 3
|
20天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
20天前
|
运维 Java 编译器
Java 异常处理:机制、策略与最佳实践
Java异常处理是确保程序稳定运行的关键。本文介绍Java异常处理的机制,包括异常类层次结构、try-catch-finally语句的使用,并探讨常见策略及最佳实践,帮助开发者有效管理错误和异常情况。
65 4
|
20天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
35 2
|
24天前
|
Java
深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
25 4
下一篇
DataWorks