多线程编程之——终止(打断)正在执行中的线程

打印 上一主题 下一主题

主题 892|帖子 892|积分 2676

多线程编程之——终止(打断)正在执行中的线程


  • ps:文字有点多,想看结果的,直接跳转:《二》
一、基础知识

1、我们基于spring开发,把线程都交给spring


  • 把线程交给spring管理好不好?
将线程交给Spring管理是一个常见的做法,特别是在基于Spring的应用程序中。通过将线程纳入Spring的管理范围,你可以利用Spring的依赖注入和生命周期管理功能,更好地控制线程的生命周期和资源。这样做也有助于避免手动管理线程带来的潜在问题,比如线程泄露和资源管理不当。但是需要注意的是,要确保在使用Spring管理线程时,遵循最佳实践,避免出现死锁和性能问题。
还有个好处:可以把spring的bean注入到线程中,然后直接使用。(如注入service完成查询数据)
如果把线程交给spring管理,注入线程的地方全是单例,如果要使用这个线程来完成两个不同的任务,需要把单例变成多例模式,spring多例模式见:SpringBoot实现多例模式

  • 一个线程默认当做执行一个任务的最小单元
所以,当你需要执行一个任务的时候,应该把参数封装到线程中,然后在线程中去执行你的任务(方法)
2、启动Thread线程是调用start还是run?

在Java中,启动一个Thread线程应该调用start方法而不是run方法。调用start方法会使Thread进入就绪状态,并让系统调度器来调用run方法。直接调用run方法只会在当前线程中执行run方法的代码,而不会创建新的线程。因此,正确的做法是调用start方法来启动一个新的线程。
3、打断执行中的线程


  • 如果我new了一个Thread线程,它一直在执行中(无限阻塞),我后面调用了interrupt打断线程,这个Thread线程会一直阻塞还是结束任务?
如果你在一个线程对象上调用interrupt方法,会设置线程的中断状态,并且如果线程正在阻塞中(比如在sleep、wait、join等方法中),它会抛出InterruptedException并清除中断状态。但如果线程正在执行普通的任务,它不会自动停止,而是需要在任务中检查中断状态并做出相应的处理。因此,你需要在任务中定期检查线程的中断状态,并在适当的时候结束任务。

  • 我调用interrupt的线程,如果一直处于睡眠中,jvm会自动回收吗?
当你调用一个处于睡眠状态的线程的interrupt方法时,它会抛出InterruptedException并清除中断状态,但并不会导致线程被自动回收。处于睡眠状态的线程会继续睡眠直到其睡眠时间结束或者被唤醒。如果你希望中断一个处于睡眠状态的线程并结束它的任务,你需要在任务中捕获InterruptedException并做出相应的处理。

  • 捕获InterruptedException并在捕获到异常时结束任务或者进行清理工作。示例:
  1. public class MyTask implements Runnable {
  2.     public void run() {
  3.         try {
  4.             // 在这里调用sleep方法使线程进入睡眠状态(模仿无限阻塞)
  5.             Thread.sleep(100000);
  6.         } catch (InterruptedException e) {
  7.             // 捕获InterruptedException并做出相应的处理
  8.             // 可以结束任务或者进行清理工作
  9.             Thread.currentThread().interrupt(); // 重新设置中断状态
  10.             return; // 结束任务
  11.         }
  12.         // 其他任务逻辑
  13.     }
  14. }
复制代码
在上面的示例中,当线程处于睡眠状态时,如果外部调用了interrupt方法,线程会抛出InterruptedException,然后在catch块中重新设置中断状态并结束任务。
要想终止线程,必须在线程中调用:Thread.currentThread().interrupt();方法,而不是外部调用。
二、打断线程

1、打断new出来的线程


  • 也可以把线程交给spring管理
  • 直接new一个线程,然后在需要打断的地方进行打断,示例:
  1. package com.cc.jschdemo.terminationThread;
  2. /**
  3. * <p>基本打断</p>
  4. *
  5. * @author cc
  6. * @since 2023/11/23
  7. */
  8. public class BasicInterrupt {
  9.     /**
  10.      * 终止
  11.      * @param args
  12.      */
  13.     public static void main(String[] args) {
  14.         //t1:模仿执行任务的线程
  15.         Thread t1 = new Thread(() -> {
  16.             try {
  17.                 //步奏1
  18.                 Thread.sleep(1000);
  19.                 System.out.println("完成:步奏1");
  20.                 //步奏2(模仿阻塞)
  21.                 while (true) {
  22.                     Thread.sleep(1000);
  23.                     System.out.println("完成:步奏2");
  24.                 }
  25.             }catch (InterruptedException e) {
  26.                 e.printStackTrace();
  27.                 //捕获异常:InterruptedException,然后打断
  28.                 //☆☆打断,终止当前线程☆☆
  29.                 Thread.currentThread().interrupt();
  30.             }
  31.         });
  32.         //t2:模仿其他操作,打断线程
  33.         Thread t2 = new Thread(() -> {
  34.             try {
  35.                 Thread.sleep(3100);
  36.                 System.out.println("打断线程!");
  37.                 //打断t1线程
  38.                 t1.interrupt();
  39.             }catch (Exception e) {
  40.                 e.printStackTrace();
  41.             }
  42.         });
  43.         //启动两个线程
  44.         t1.start();
  45.         t2.start();
  46.     }
  47. }
复制代码

  • 测试结果:步奏1执行1次,步奏2执行两次,然后t1线程就会被t2线程终止
  • 线程被打断会抛出错误:java.lang.InterruptedException: sleep interrupted

2、打断注入spring的线程


  • 新建线程,注入spring中
  • ps:注意,注入spring的线程是单例的,实际使用过程中,我们都是一个线程一个任务,如果需要不同的对象,可以开启spring的多例,见:SpringBoot实现多例模式
  1. package com.cc.jschdemo.terminationThread;
  2. import lombok.Data;
  3. import lombok.EqualsAndHashCode;
  4. import org.springframework.stereotype.Component;
  5. /**
  6. * <p>注入spring的线程</p>
  7. *
  8. * @author --
  9. * @since 2023/11/24
  10. */
  11. @EqualsAndHashCode(callSuper = true)
  12. @Data
  13. @Component
  14. public class SpringThread extends Thread{
  15.     //1可以注入Spring的Bean,然后直接使用
  16. //    @Resource
  17. //    private IBasicInterruptService basicInterruptService;
  18.     //2可以直接传入执行任务的参数(可多个)
  19.     private String footwork;
  20.     /**
  21.      * 具体要执行的任务
  22.      */
  23.     @Override
  24.     public void run() {
  25.         try {
  26.             //步奏1
  27.             Thread.sleep(1000);
  28.             System.out.printf("完成:%s 1 %n", footwork);
  29.             //步奏2(模仿阻塞)
  30.             while (true) {
  31.                 Thread.sleep(1000);
  32.                 System.out.printf("完成:%s 2 %n", footwork);
  33.             }
  34.         }catch (InterruptedException e) {
  35.             e.printStackTrace();
  36.             //捕获异常:InterruptedException,然后打断
  37.             //☆☆打断,终止当前线程☆☆
  38.             Thread.currentThread().interrupt();
  39.         }
  40.     }
  41. }
复制代码

  • 创建任务,模仿打断
  1.     //可以直接注入线程
  2.     @Resource
  3.     private SpringThread springThread;
  4.    
  5.     //模仿创建任务、打断
  6.     @Test
  7.     public void test07()throws Exception{
  8.         try {
  9.             ///传入执行任务,需要的参数
  10.             springThread.setFootwork("步奏");
  11.             //启动线程
  12.             springThread.start();
  13.             Thread.sleep(3100);
  14.             System.out.println("打断线程!");
  15.             //打断t1线程
  16.             springThread.interrupt();
  17.         }catch (Exception e) {
  18.             e.printStackTrace();
  19.         }
  20.     }
复制代码

  • 结果:和《打断new出来的线程》一模一样

3、打断CompletableFuture启动的线程


  • CompletableFuture的cancel(true)方法无法打断
  • 调用线程的Thread task1,的interrupt也无法打断
  • CompletableFuture.runAsync(task1)启动线程task1的底层逻辑是:CompletableFuture调用ForkJoinPool线程池启动一个线程(A)去执行task1任务。
  • 所以我们要打断的不是task1,而是要打断A
  1. package com.cc.jschdemo.terminationThread;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.PostMapping;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import java.util.concurrent.CompletableFuture;
  7. /**
  8. * <p></p>
  9. *
  10. * @author --
  11. * @since 2023/11/25
  12. */
  13. @RestController
  14. @RequestMapping("/completableFuture")
  15. public class CompletableFutureThread {
  16.     //这里使用标记打断的话,每个CompletableFuture都需要一个标记,可以(CompletableFuture和标记)一起缓存下来。
  17.     boolean flagTask1 = false;
  18.     boolean flagTask2 = false;
  19.     //模仿任务1线程,也可以是:Runnable
  20.     Thread task1 = new Thread(() -> {
  21.         try {
  22.             //模仿线程无限阻塞(while (true))
  23.             while (true) {
  24.                 System.out.println("任务1执行...");
  25.                 try {
  26.                     Thread.sleep(1000);
  27.                 } catch (InterruptedException e) {
  28.                     throw new RuntimeException(e);
  29.                 }
  30.                 //检查终止状态,如果终止就报错。true已中断;false未中断
  31.                 // 这里获取到的是:CompletableFuture启动线程池的线程池的线程。就是执行task1的线程
  32.                 boolean interrupted = Thread.currentThread().isInterrupted();
  33.                 // 所以interrupted不能用来终止task1
  34.                 //这里使用标记来终止task1(可以根据业务,在需要终止的地方设置这个打断。)
  35.                 if (flagTask1) {
  36.                     //打断:CompletableFuture启动线程池的线程池的线程
  37.                     Thread.currentThread().interrupt();
  38.                 }
  39.             }
  40.         }catch(Exception e){
  41.             e.printStackTrace();
  42.         }
  43.     });
  44.     //任务2
  45.     Thread task2 = new Thread(() -> {
  46.         try {
  47.             //模仿线程阻塞
  48.             while (true) {
  49.                 System.out.println("任务2执行...");
  50.                 try {
  51.                     Thread.sleep(1000);
  52.                 } catch (InterruptedException e) {
  53.                     throw new RuntimeException(e);
  54.                 }
  55.                 //这里使用标记来终止task1
  56.                 if (flagTask2) {
  57.                     Thread.currentThread().interrupt();
  58.                 }
  59.             }
  60.         }catch(Exception e){
  61.             e.printStackTrace();
  62.         }
  63.     });
  64.     //缓存CompletableFuture
  65.     CompletableFuture<Void> future1;
  66.     CompletableFuture<Void> future2;
  67.     //启动任务
  68.     @GetMapping
  69.     public void test08()throws Exception{
  70.         //实际使用中,可以使用循环添加CompletableFuture,然后记录下future(缓存),在要中断的逻辑中调用cancel
  71.         //1开启task任务。这里实际是CompletableFuture调用ForkJoinPool线程池启动一个线程去执行task1任务。
  72.         //要想终止任务,要终止CompletableFuture调用ForkJoinPool线程池创建的线程,而非task1线程。
  73.         future1 = CompletableFuture.runAsync(task1);
  74.         future2 = CompletableFuture.runAsync(task2);
  75.     }
  76.     //终止任务
  77.     @PostMapping
  78.     public void stop() {
  79.         //3取消任务
  80.         // 参数true表示尝试中断任务执行
  81.         // cancel方法会尝试取消任务的执行,参数true表示尝试中断任务执行。
  82.         // 成功取消返回true,否则返回false。
  83.         // 需要注意的是,cancel方法并不会直接中断正在执行的线程,而是会尝试取消任务的执行,
  84.         // 具体是否成功取决于任务的实现。
  85.         //终止方式1:使用cancel终止:无法终止task1
  86.         boolean cancel1 = future1.cancel(Boolean.TRUE);
  87.         System.out.println("任务1终止:" + (cancel1 ? "成功1" : "失败1"));
  88.         //终止方式2:打断线程task1:无法终止task1
  89.         task1.interrupt();
  90.         //睡5s,让任务跑5s,看终止方式1、2是否能终止,测试结果为:不能终止
  91.         try {
  92.             Thread.sleep(5000);
  93.         } catch (InterruptedException e) {
  94.             throw new RuntimeException(e);
  95.         }
  96.         //终止方式3:直接标记打断。CompletableFuture调用ForkJoinPool线程池生成的线程
  97.         // 真正的终止task1
  98.         flagTask1 = true;
  99.     }
  100. }
复制代码

  • 测试
  • 终止方式1/2都执行了。但是任务没有终止


  • 终止方式3执行后,才真正的终止了。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

怀念夏天

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表