• JUC并发编程第五篇,如何优雅的使用线程中断机制和线程等待唤醒机制?


    一、线程中断机制

    1. 什么是线程中断?

    一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。

    • Java提供了一种用于停止线程的机制——中断。
    • 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
    • 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true,接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。

    2. 你知道 interrupt() 方法的含义吗?

    在这里插入图片描述

    • public void interrupt()

    实例方法interrupt()仅仅是设置线程的中断状态为true,不会停止线程。

    • public boolean isInterrupted()

    通过检查中断标志位,判断当前线程是否被中断。

    • public static boolean interrupted()

    静态方法,判断线程是否被中断,并清除当前中断状态
    也就是说这个方法做了两件事:
    1、返回当前线程的中断状态
    2、将当前线程的中断状态设为false

    3. 如何使用中断标识优雅的停止线程?

    第一种:通过volatile变量实现
    static volatile boolean isStop = false;
    
    public static void volatileDemo() {
            new Thread(() -> {
                while(true) {
                    if(isStop) {
                        System.out.println("-----程序结束------");
                        break;
                    }
                    System.out.println("------hello-------");
                }
            },"t1").start();
    
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    
            new Thread(() -> {
                isStop = true;
            },"t2").start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    第二种:通过AtomicBoolean实现
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
    
    public static void atomicBooleanDemo() {
            new Thread(() -> {
                while(true) {
                    if(atomicBoolean.get()) {
                        System.out.println("-----程序结束------");
                        break;
                    }
                    System.out.println("------hello-------");
                }
            },"t1").start();
            
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    
            new Thread(() -> {
                atomicBoolean.set(true);
            },"t2").start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    第三种:通过线程自带 interrupt() 方法实现
    public static void m3() {
            Thread t1 = new Thread(() -> {
                while (true) {
                    //当前线程判断中断标志
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("-----程序结束------");
                        break;
                    }
                    System.out.println("------hello------");
                }
            }, "t1");
    
            t1.start();
    
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    
            new Thread(() -> {
                //修改t1线程的中断标志位为true
                t1.interrupt();
            },"t2").start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4. 当前线程的中断标识为true,就立刻停止了吗?

    先说结论:中断只是一种协同机制,只修改中断标识位而已,不会立刻停止线程

    • 代码证明:
    public static void stopDemo() {
            Thread t1 = new Thread(() -> {
                for (int i = 1; i <= 300; i++) {
                    System.out.println("------i: " + i);
                }
                System.out.println("t1.interrupt()调用之后02: "+Thread.currentThread().isInterrupted());
            }, "t1");
            t1.start();
    
            System.out.println("t1.interrupt()调用之前,t1线程的中断标识默认值: "+t1.isInterrupted());
            try { TimeUnit.MILLISECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            //实例方法interrupt()仅仅是设置线程的中断状态位设置为true,不会停止线程
            t1.interrupt();
            //活动状态,t1线程还在执行中
            System.out.println("t1.interrupt()调用之后01: "+t1.isInterrupted());
    
            try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
            //非活动状态,t1线程不在执行中,已经结束执行了。
            System.out.println("t1.interrupt()调用之后03: "+t1.isInterrupted());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述在这里插入图片描述在这里插入图片描述

    还有一种情况:如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

    public static void stopDemo2() {
            Thread t1 = new Thread(() -> {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("-----isInterrupted() = true,程序结束。");
                        break;
                    }
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        //线程的中断标志位为false,无法停下,需要再次掉interrupt()设置true
                        //Thread.currentThread().interrupt();
                        e.printStackTrace();
                    }
                    System.out.println("------hello Interrupt");
                }
            }, "t1");
    
            t1.start();
    
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
    
            new Thread(() -> {
                t1.interrupt();//修改t1线程的中断标志位为true
            },"t2").start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    上边不能停止的原因是:
    抛出了中断异常后,中断标识也被置为了 false ,导致无限循环,想要解决这个bug,需要在异常中再次中断,也就是打开上边的 //Thread.currentThread().interrupt(); 这句代码。

    二、线程等待唤醒机制,让线程等待和唤醒的3种方法注意事项

    第一种:Object中的 wait() 方法让线程等待,notify() 方法唤醒线程

    public static void syncWaitNotify() {
            new Thread(() -> {
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectLock){
                    System.out.println(Thread.currentThread().getName()+"\t"+"---come in");
                    try {
                        objectLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒");
                }
            },"t1").start();
    
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    
            new Thread(() -> {
                synchronized (objectLock){
                    objectLock.notify();
                    System.out.println(Thread.currentThread().getName()+"\t"+"---发出通知");
                }
            },"t2").start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    使用总结:
    wait和notify方法必须要在同步块或者方法里面,且成对出现使用
    需要先 wait 后 notify 才能生效

    第二种:JUC包中 Condition 的 await() 方法让线程等待,signal() 方法唤醒线程

    public static void lockAwaitSignal() {
            new Thread(() -> {
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
                lock.lock();
                try
                {
                    System.out.println(Thread.currentThread().getName()+"\t"+"---come in");
                    condition.await();
                    System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            },"t1").start();
    
            new Thread(() -> {
                lock.lock();
                try
                {
                    condition.signal();
                    System.out.println(Thread.currentThread().getName()+"\t"+"---发出通知");
                }finally {
                    lock.unlock();
                }
            },"t2").start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    使用总结:
    Condtion中的线程等待和唤醒方法之前,需要先获取锁
    需要先 await 后 signal 才能生效

    第三种:LockSupport类中的park等待和unpark唤醒

    • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。
    public static void parkDemo() {
            Thread t1 = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t" + "---come in");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒");
            }, "t1");
            
            t1.start();
    
            new Thread(() -> {
                LockSupport.unpark(t1);
                System.out.println(Thread.currentThread().getName()+"\t"+"---发出通知");
            },"t2").start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    总结:park() 阻塞线程,unpark(Thread thread) 唤醒线程,不需要像之前那样锁块,也没有先后顺序的要求
    注意1:注意多次调用 unpark() 方法,不会累加,permit值最大是1
    注意2:一个线程只能发一张通行证,比如 t1 park一次,t2可以 unpark 唤醒,如果 t1 park 了两次,就需要两个线程 t2 park 一次,t3 park 一次,以此类推,一个线程只能唤醒一次阻塞。

  • 相关阅读:
    A2l文件解析
    大语言模型助力审计问题自动定性
    Rest Template 使用
    [c语言]这c语言代码就像做完形填空一样
    分布式主键算法
    Ai绘画描述词 关键词大全 真人美女 二次元卡通美女 国漫动漫效果
    Redis-SpringBoot实战与缓存问题
    Rust 如何优雅关闭 channel
    人工神经网络的基本模型,人工神经网络数学模型
    【Python】给定一个长度为n的数列,将这个数列按从小到大的顺序排列。1<=n<=200
  • 原文地址:https://blog.csdn.net/NICK_53/article/details/128067258