• Java多线程(Thread)详解之启动与中断


    在我的前一篇博客中直接介绍了Thread的”五种“打开方式:Thread的”五种“打开方式icon-default.png?t=N7T8https://blog.csdn.net/qq_45875349/article/details/132644717?spm=1001.2014.3001.5501

    但是还没有详细的对Thread类进行说明,这篇博客主要对Thread类进行介绍,然后分享一下自己对start()interrupt()的理解。

    目录

    1 Thread的常见构造方法

     2 Thread的几个常见属性

    3 启动一个线程-start()

    4 中断一个线程

    4.1 通过共享的标记来进行沟通

    4.2 调用 interrupt() 方法来通知

    4.3 不同方法下观察Thread标志位是否被重置


    1 Thread的常见构造方法

    方法     说明
    Thread()
    创建线程对象
    Thread(Runnable target) 使用 Runnable 对象创建线程对象
    Thread(String name) 创建线程对象,并命名
    Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名
    1. Thread t1 = new Thread();
    2. Thread t2 = new Thread(new MyRunnable());
    3. Thread t3 = new Thread("这是我的名字");
    4. Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

    说明:t1\t2\t3\t4只是线程的变量名而不是真正意义的线程的名字,t只是代码中对于thread的引用,thread的真正名字是指的是Thread类中的name属性的值。我们可以通过jconsole工具来查看通过Thread(String name) 构造方法是不是成功对thread进行了“命名”。

     2 Thread的几个常见属性

    属性获取方法
    ID
    getId()
    名称 getName()
    状态 getState()
    优先级 getPriority()
    是否后台线程 isDaemon()
    是否存活 isAlive()
    是否被中断

    isInterrupted() 

    ID (getId): getId方法返回线程的唯一标识符,通常作为一个long。

    1. long id = thread.getId();
    2. System.out.println(id);

    名称 (getName): getName方法用于获取线程的名称,可以设置线程的名称以便于标识和调试。

    1. String name = thread.getName();
    2. System.out.println(name);

    状态 (getState): getState方法用于获取线程的当前状态,通常返回一个Thread.State枚举值,表示线程状态。常见的状态包括 NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED。具体的内容会在后面继续深入讲解。

    1. Thread.State threadState = thread.getState();
    2. System.out.println(threadState);

    优先级 (getPriority): getPriority方法返回线程的优先级,用于表示线程的相对重要性。线程的优先级通常在1到10之间,其中1表示最低优先级,10表示最高优先级。理论上可以干预线程调度,但是实际线程的调度有很多复杂的因素糅合在一起决定,不一定按照我们自己的预期去执行。

    1. int threadPriority = thread.getPriority();
    2. System.out.println(threadPriority);

    是否后台线程 (isDaemon): isDaemon方法用于检查线程是否为后台线程(守护进程)。后台线程在所有非后台线程结束时自动终止。默认情况下,线程是非后台的。

    boolean isDaemonThread = thread.isDaemon();

    是否存活 (isAlive): isAlive方法检查线程是否还在运行。如果线程已经终止或还没有启动,它将返回false,否则返回true

    boolean isThreadAlive = thread.isAlive();
    1. public class ThreadUse {
    2. public static void main(String[] args) {
    3. Thread thread = new Thread(() -> {
    4. for (int i = 0; i < 3; i++) {
    5. System.out.println("我在工作..");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. },"t1");
    13. thread.start();
    14. long id = thread.getId();
    15. System.out.println(id);
    16. String name = thread.getName();
    17. System.out.println(name);
    18. Thread.State threadState = thread.getState();
    19. System.out.println("status:" + threadState);
    20. int threadPriority = thread.getPriority();
    21. System.out.println(threadPriority);
    22. System.out.println("isDeamon:"+thread.isDaemon());
    23. System.out.println("isAlive:" + thread.isAlive());
    24. try {
    25. Thread.sleep(8000);
    26. } catch (InterruptedException e) {
    27. e.printStackTrace();
    28. }
    29. System.out.println("isAlive:" + thread.isAlive());
    30. System.out.println("status:" + threadState);
    31. }
    32. }

    3 启动一个线程-start()

    在上一篇博客中我们已经看到了如何通过覆写 run 方法创建一个线程对象, 但线程对象被创建出来并不意味着线程就开始运行了。
    • 覆写 run 方法是提供给线程要做的事情的指令清单
    • 线程对象可以认为是张三(主线程)把 李四、王五(新线程)叫过来了
    • 而调用 start() 方法,就是喊一声:行动起来!“,线程才真正独立去执行了(才真的在操作系统的底层创建出一个线程.)。

    4 中断一个线程

    举一个例子,比如我正在打游戏(执行线程)现在我的妈妈喊我去吃饭(通知中断线程),但是我不一定就立马去吃饭(中断线程),我有可能打一会游戏之后就去吃饭,也有可能直接忽视喊我吃饭一直玩下去。编程中的中断也需要类似的业务那么需要怎么实现呢?怎么通知线程中断呢?这就涉及到停止线程的方式了。

    目前通知线程中断常用的方法有两种:

    • 1. 通过共享的标记来进行沟通
    • 2. 调用 interrupt() 方法来通知

    4.1 通过共享的标记来进行沟通

    示例1:使用自定义的变量作为标志位。需要给标志位加上volatile关键字。

    why使用volatile:

    在多线程编程中,如果多个线程需要共享一个变量来进行沟通、协作或同步操作,那么需要特别注意线程间的可见性问题。这是因为不同的线程可能在不同的 CPU 缓存中缓存了相同的变量,导致一个线程修改了变量的值,但其他线程可能不会立即看到这个变化,从而导致不一致的行为。

    volatile 关键字用于解决变量的可见性问题。当一个变量被声明为 volatile 时,Java 内存模型确保每次读取这个变量时都会直接从主内存中获取最新的值,每次修改这个变量时都会立即将新值写入主内存,而不会使用线程的本地缓存。这就确保了变量的修改对其他线程是可见的。

    在使用共享标志位来进行线程协作或同步时,通常需要将这个标志位声明为 volatile,以确保任何一个线程对标志位的修改都能够被其他线程立即看到。

    1. public class Thread_interrupt2 {
    2. private static volatile boolean flag = false;
    3. public static void main(String[] args) {
    4. Thread thread = new Thread(() -> {
    5. while (!flag) {
    6. System.out.println("打游戏...");
    7. try {
    8. Thread.sleep(1000);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. }
    13. });
    14. thread.start();
    15. try {
    16. Thread.sleep(2*1000);
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. System.out.println("要吃饭了,别打游戏了!");
    21. flag = true;
    22. }
    23. }

    结果:

    4.2 调用 interrupt() 方法来通知

    示例 -2 : 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位 .
    Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记 . 就像刚才我们自己写的代码一样。
    1. public static void main(String[] args) {
    2. Thread working = new Thread() {
    3. @Override
    4. public void run() {
    5. while (!Thread.currentThread().isInterrupted()) {
    6. System.out.println("working");
    7. try {
    8. Thread.sleep(1000);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. //在中断前处理的业务
    12. System.out.println("做工作交接...");
    13. }
    14. }
    15. }
    16. };
    17. working.start();
    18. //让线程工作5秒
    19. try {
    20. Thread.sleep(5000);
    21. } catch (InterruptedException e) {
    22. e.printStackTrace();
    23. }
    24. //通知线程该结束了
    25. System.out.println("通知线程可以结束工作了!");
    26. working.interrupt();
    27. }
    28. }

    这样就可以让线程停止下来吗??很遗憾并不是,看看执行的结果:

    为什么会出现这样的结果呢?

    在Java中,使用thread.interrupt()方法来通知线程中断的行为是正确的,但线程是否真正中断取决于线程本身的实现以及它的中断状态如何被检查。interrupt()方法仅仅是设置了线程的中断状态,它并不会强制停止线程执行。线程需要自己在适当的时候检查中断状态并决定是否终止执行。

    当线程处于sleep状态时,如果在另一个线程中调用了thread.interrupt()来中断正在sleep的线程,会导致sleep方法抛出InterruptedException异常。但是,这并不会立即停止线程的执行,而是会将中断状态重置为false,同时抛出异常。线程需要捕获这个异常并决定是否终止执行。

     也就是说interrupt()方法会导致sleep方法抛出InterruptedException异常, 在抛出异常的同时还会将中断状态重置为false,所以导致了上面的现象。而我们调用interrupt()方法是想将中断状态置为true的,所以最后没有达到我们预期的效果。

    既然如此那么该怎么达到最终的中断效果呢?在上面的代码中我们可以在捕获异常后使用return或者break跳出循环即可。在使用return或者break语句之前可以做一些最后的逻辑。

    1. public class Thread_interrupt {
    2. public static void main(String[] args) {
    3. Thread working = new Thread() {
    4. @Override
    5. public void run() {
    6. while (!Thread.currentThread().isInterrupted()) {
    7. System.out.println("working");
    8. try {
    9. Thread.sleep(1000);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. //在中断前处理的业务
    13. System.out.println("做工作交接...");
    14. //结束任务了
    15. System.out.println("结束工作");
    16. return;
    17. }
    18. }
    19. }
    20. };
    21. working.start();
    22. //让线程工作5秒
    23. try {
    24. Thread.sleep(5000);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. }
    28. //通知线程该结束了
    29. System.out.println("通知线程可以结束工作了!");
    30. working.interrupt();
    31. }
    32. }

     4.3 不同方法下观察Thread标志位是否被重置

    现在出现了三个容易混淆的方法:

    方法说明
    public void interrupt()
    中断对象关联的线程,如果线程正在阻塞,则以异常方式通知同时重置标志位
    public static boolean interrupted()
    判断当前线程的中断标志位是true还是false,调用后重置标志位为false
    public boolean isInterrupted()
    判断对象关联的线程的标志位true还是false,调用后不重置标志位

     thread收到通知的方式有两种:

    1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通
    知, 重置中断标志
            当出现 InterruptedException 的时候 , 要不要结束线程取决于 catch 中代码的写法 . 可以选择
            忽略这个异常, 也可以跳出循环结束线程(上面代码以及展示了)
    2. 否则,只是内部的一个中断标志被设置, thread 可以通过
    • Thread.interrupted() 判断当前线程的中断标志的设置,重置中断标志为false
    1. public class ThreadFlag {
    2. public static void main(String[] args) {
    3. Thread thread = new Thread(() -> {
    4. for (int i = 0; i < 10; i++) {
    5. System.out.println(Thread.interrupted());
    6. }
    7. });
    8. thread.start();
    9. //通知中断 此时对应第二钟情况 没有因为调用 wait/join/sleep 等方法而阻塞挂起
    10. thread.interrupt();
    11. }
    12. }

     运行结果:

    • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不重置中断标志这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。
    1. public class ThreadFlag {
    2. public static void main(String[] args) {
    3. Thread thread = new Thread(() -> {
    4. for (int i = 0; i < 10; i++) {
    5. System.out.println(Thread.currentThread().isInterrupted());
    6. }
    7. });
    8. thread.start();
    9. //通知中断 此时对应第二钟情况 没有因为调用 wait/join/sleep 等方法而阻塞挂起
    10. thread.interrupt();
    11. }
    12. }

    运行结果:

     然后我们就可以通过这个中断标记来自由实现线程具体是否中断的业务了。

    到这里对于interrupt是不是豁然开朗了!

  • 相关阅读:
    沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置
    C 嵌入式系统设计模式 20:队列模式
    2023影视源接口分享 影视仓配置接口大全 TVBox接口地址源 订阅源地址大全
    Spring:@Autowired @Resource @Value的区别
    Linux网络编程8——线程池模型
    1688商品详情接口代码展示
    基于Spring Boot的LDAP开发全教程
    Qt中https的使用,报错TLS initialization failed和不能打开ssl.lib问题解决
    HarmonyOS系统中内核实现烟雾检测的方法
    【maven下载、安装、配置教程】
  • 原文地址:https://blog.csdn.net/qq_45875349/article/details/132678908