• 【JavaEE多线程】线程中断 interrupt()


    系列文章目录

    🌈座右铭🌈:人的一生这么长、你凭什么用短短的几年去衡量自己的一生!

    💕个人主页:清灵白羽 漾情天殇_计算机底层原理,深度解析C++,自顶向下看Java-CSDN博客

    ❤️相关文章❤️:清灵白羽 漾情天殇-CSDN博客



    一、终止一个线程

    1、标记位终止线程

            在Java当中通过共享的标记位来终止线程是一种常见的方式,这种方式通常设计一个布尔类型的变量,通常被称为标记或者标志,用来指示线程是否应该终止,线程在执行任务的时候会周期性地检查这个标记,并且标记指示终止时主动退出执行。

            1、定义一个共享的标记变量

            

    private volatile boolean shouldTerminate = false;
    

            这里使用了volatile这个关键字,后续我会为大家详细介绍这个关键字的用法,大家这里先暂时忽略它。

            2、在线程的执行任务当中周期性检查标记

            在线程的任务当中通过周期性地检查标记来确定是否应该被终止,通常在循环当中检查标记。

         

    1. public void run() {
    2. while (!shouldTerminate) {
    3. // 执行任务
    4. }
    5. }

            当这个标记位为true的时候,线程就会退出循环,从而终止执行任务。

            3、提供公共方法来设置标记

            在需要终止线程的时候,提供一个公共方法来设置标记为true。

    1. public void requestTermination() {
    2. shouldTerminate = true;
    3. }

            4、代码展示标记位终止线程

    1. public class Main{
    2. private static class MyRunnable implements Runnable{
    3. private volatile boolean shouldTerminate = false;
    4. @Override
    5. public void run() {
    6. while (!shouldTerminate){
    7. System.out.println("Thread is running...");
    8. try {
    9. Thread.sleep(1000);
    10. } catch (InterruptedException e) {
    11. throw new RuntimeException(e);
    12. }
    13. }
    14. System.out.println("Thread terminated...");
    15. }
    16. public void requestTermination(){
    17. shouldTerminate = true;
    18. }
    19. }
    20. public static void main(String[] args) {
    21. MyRunnable myRunnable = new MyRunnable();
    22. Thread thread = new Thread(myRunnable);
    23. thread.start();
    24. try {
    25. Thread.sleep(5000);
    26. } catch (InterruptedException e) {
    27. throw new RuntimeException(e);
    28. }
    29. myRunnable.requestTermination();
    30. }
    31. }

            在我的示例代码当中,MyRunnable类声明为静态内部类,有以下几个原因:

            1、静态内部类可以直接访问外部类的静态成员

            2、静态内部类的实例化不依赖外部类的实例

            3、静态内部类可以更方便地重用和单独测试:将M有Runnable声明为静态内部类可以使得它更容易被其它类重用,并且可以在不依赖外部类的情况下单独测试。

    2、调用interrupt()方法

            我在这里要声明以下只有interrupt方法才能中断线程,其余两个方法只是检查线程当前的线程状态的,这里一定不要混淆。    

          1、interrupt()方法

    • interrupt()方法是Thread类的实例方法,用于向目标线程发送中断信号,即设置目标线程的中断状态为true。
    • 如果目标线程正在阻塞(如调用了sleep()wait()join()等方法),那么会抛出InterruptedException异常。
    • 如果目标线程正在运行,那么只是简单地设置其中断状态为true,线程需要在合适的时机自行检查中断状态并作出响应。

          2、interrupted()方法

            这里的方法其实就是系统为我们自动维护的一个标记位,不需要我们在手动维护了,一会我写代码各位就能够看懂了。

    • interrupted()是Thread类的静态方法,用于检查当前线程的中断状态,并清除中断状态(将中断状态重置为false)。
    • 如果当前线程的中断状态为true,则返回true;否则返回false。
    • interrupted()方法还可以用来检查其他线程的中断状态,即Thread.interrupted()会检查调用它的线程的中断状态。

            3、isInterrupted()方法

    • isInterrupted()是Thread类的实例方法,用于检查目标线程的中断状态,但不会清除中断状态。
    • 如果目标线程的中断状态为true,则返回true;否则返回false。
    • isInterrupted()方法可以用来检查其他线程的中断状态,即通过thread.isInterrupted()来检查指定线程的中断状态。

            总结:

    • interrupt()方法是设置线程中断状态为true的方法,用于向目标线程发送中断信号。
    • interrupted()方法是用于检查当前线程中断状态并清除中断状态。
    • isInterrupted()方法是用于检查目标线程中断状态,但不会清除中断状态。

            4、如何判断应该使用哪种方法呢?

    • 如果你希望向一个线程发送中断信号,让它在合适的时候自行终止执行,那么使用interrupt()方法是比较合适的。这种方式允许线程自行决定如何响应中断信号,可以更安全地终止线程的执行。

    • 如果你需要在一个线程中检查自身的中断状态,并清除中断状态,可以使用interrupted()方法。这种方式适合在执行任务的线程中周期性地检查中断状态,以便及时响应中断信号并执行相应的清理操作。

    • 如果你需要检查其他线程的中断状态,而且不想清除中断状态,可以使用isInterrupted()方法。这种方式适合在一个线程中检查其他线程的中断状态,例如在监控线程池中线程的执行情况时。

            5、什么情况下需要清除中断状态,又为什么要清除中断状态呢?

            清除中断状态通常发生在一些特定的情况下,这些情况通常涉及到线程的异常处理或者线程的复用。

            1、异常处理:在捕获了InterruptedException异常之后,需要清除线程的中断状态。

            2、线程复用:当一个线程执行完某个任务后,可能需要复用该线程来执行其他任务。

            清除中断状态就是将线程判断是否中断的标记位设置为初始值也就是false,清除中断状态就是告诉其它线程这个线程没有被中断。

            在Java中,当线程被中断时,它的中断状态会被设置为true。这个中断状态可以通过调用Thread.interrupted()isInterrupted()方法来检查。

            InterruptedException异常通常是在线程调用了一些可能会抛出InterruptedException的阻塞方法(例如Thread.sleep()、Object.wait()、Thread.join()等)时才会抛出。当线程处于阻塞状态(例如调用了上述方法),而且此时又接收到了中断请求时,这些阻塞方法会抛出InterruptedException异常。这样做的目的是让线程在阻塞状态中响应中断请求,从而提高线程的响应性。因为阻塞的线程无法响应中断请求这个时候抛出异常并且在抛出异常之前系统会自动清除中断状态,让这个目标线程以非中断的状态去响应这个中断。并不是所有的中断请求都会导致InterruptedException异常的抛出。如果线程正在执行非阻塞的任务,而此时接收到中断请求,那么线程就不会抛出InterruptedException异常,而是需要通过检查中断状态来判断是否应该终止执行。

            上述的interrupted()方法是手动清除中断状态,抛出异常这个操作是系统自动清除中断状态,不管怎样目的都是为了清除中断状态让线程继续执行剩余的业务逻辑。

    1. public class Main {
    2. public static void main(String[] args) {
    3. Thread thread = new Thread(new MyRunnable());
    4. thread.start();
    5. try {
    6. Thread.sleep(2000);
    7. thread.interrupt();
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. static class MyRunnable implements Runnable{
    13. @Override
    14. public void run() {
    15. System.out.println("子线程开始执行...");
    16. try {
    17. Thread.sleep(5000);
    18. System.out.println("子线程睡眠结束...");
    19. } catch (InterruptedException e) {
    20. System.out.println("子线程被中断...");
    21. e.printStackTrace();
    22. }
    23. if (Thread.currentThread().isInterrupted()){
    24. System.out.println("未被清除!");
    25. }else {
    26. System.out.println("已被清除");
    27. }
    28. }
    29. }
    30. }

    拓展:

            

    1. public class Main {
    2. private static class MyRunnable implements Runnable{
    3. @Override
    4. public void run() {
    5. while (!Thread.currentThread().isInterrupted()){
    6. System.out.println("线程运行中");
    7. try {
    8. Thread.sleep(1000);
    9. } catch (InterruptedException e) {
    10. System.out.println("中断线程");
    11. }
    12. }
    13. System.out.println("线程已经中断");
    14. }
    15. }
    16. public static void main(String[] args) {
    17. Thread thread = new Thread(new MyRunnable());
    18. thread.start();
    19. try {
    20. Thread.sleep(5000);
    21. } catch (InterruptedException e) {
    22. throw new RuntimeException(e);
    23. }
    24. thread.interrupt();
    25. }
    26. }

            这段代码大家可以试着运行一下,调用interrupt()无法终止线程,当控制台输出中断线程之后,线程会继续执行,这是为什么呢?因为线程在休眠状态下sleep()的时候,系统为了让这个线程响应中断请求,会将他唤醒,并且在抛出异常之前自动清除这个线程的中断状态,也就是这个时候线程的标记位仍然为false,所以这个时候我们必须对这个线程的中断状态进行恢复或者直接使用break关键字终止线程,这里还是推荐大家使用恢复线程中断状态比较好,修改以后的代码如下:

    Thread.currentThread().interrupt();

            只需要添加这一行代码即可:

    1. public class Main {
    2. private static class MyRunnable implements Runnable{
    3. @Override
    4. public void run() {
    5. while (!Thread.currentThread().isInterrupted()){
    6. System.out.println("线程运行中");
    7. try {
    8. Thread.sleep(1000);
    9. } catch (InterruptedException e) {
    10. System.out.println("中断线程");
    11. Thread.currentThread().interrupt();
    12. e.printStackTrace();
    13. }
    14. }
    15. System.out.println("线程已经中断");
    16. }
    17. }
    18. public static void main(String[] args) {
    19. Thread thread = new Thread(new MyRunnable());
    20. thread.start();
    21. try {
    22. Thread.sleep(5000);
    23. } catch (InterruptedException e) {
    24. throw new RuntimeException(e);
    25. }
    26. thread.interrupt();
    27. }
    28. }

            我们使用这行代码将线程的中断状态恢复,线程就可以顺利中断了。


    二、线程等待

             

            join()方法是 Thread 类提供的一个方法,用于让一个线程等待另一个线程的结束。具体来说,当一个线程调用另一个线程的 join() 方法时,它会阻塞自己的执行,直到被等待的线程执行结束才继续执行。

    这个方法提供了一种线程之间的协作机制,允许一个线程等待另一个线程完成某些重要的工作,然后再继续执行。它通常用于主线程等待子线程的完成,或者一个线程等待其他一组线程的完成。

    join() 方法有多个重载形式:

    1. join():使当前线程等待被调用对象的执行完成。
    2. join(long millis):使当前线程等待被调用对象的执行完成,但最多等待指定的毫秒数。
    3. join(long millis, int nanos):使当前线程等待被调用对象的执行完成,但最多等待指定的毫秒数和纳秒数。

    使用 join() 方法的一个常见模式是创建一个线程并启动它,然后在主线程中调用该线程的 join() 方法,以等待该线程的完成。例如:

    1. public class Main {
    2. public static void main(String[] args) {
    3. Thread thread = new Thread(new MyRunnable());
    4. thread.start(); // 启动子线程
    5. try {
    6. thread.join(); // 主线程等待子线程执行完成
    7. } catch (InterruptedException e) {
    8. e.printStackTrace();
    9. }
    10. System.out.println("子线程执行完成,主线程继续执行...");
    11. }
    12. static class MyRunnable implements Runnable {
    13. @Override
    14. public void run() {
    15. System.out.println("子线程开始执行...");
    16. // 模拟子线程执行一些耗时操作
    17. try {
    18. Thread.sleep(3000);
    19. } catch (InterruptedException e) {
    20. e.printStackTrace();
    21. }
    22. System.out.println("子线程执行完成。");
    23. }
    24. }
    25. }

  • 相关阅读:
    boa交叉编译(移植到arm)
    P2607 [ZJOI2008] 骑士 (树形dp
    若依+lodop+jasperreports+ireport 设计打印票据格式(一)
    【Interconnection Networks 互连网络】Dragonfly Topology 蜻蜓网络拓扑
    PostgreSQL ash —— pgsentinel插件
    二进制搭建 Kubernetes v1.20
    在WinForms应用程序中创建一个定时任务以监听鼠标左键点击事件可以通过以下步骤实现
    react生命周期钩子函数
    C++八股
    【6k字】详解Python装饰器和生成器
  • 原文地址:https://blog.csdn.net/weixin_59658448/article/details/137974680