首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >JUC - 线程中断与线程等待、唤醒(LockSupport)

JUC - 线程中断与线程等待、唤醒(LockSupport)

作者头像
鱼找水需要时间
发布2023-02-16 17:14:11
发布2023-02-16 17:14:11
1.3K0
举报
文章被收录于专栏:SpringBoot教程SpringBoot教程

中断机制

什么是中断机制?

​ 首先 ​ 一个线程不应该由其他线程来强制中断或停止,而是应该有线程自己自行停止,自己来决定自己的命运。 ​ 所以,Thread.stop, Thead.suspend, Thead.resumer都已经被废弃了。

​ 其次 ​ 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。 ​ 因此,Java提供了一种用于停止线程的协商机制–中断,即中断标识协商机制。

中断只是一种协商协作机制,Java中没有给中断增加任何语法,中断的过程完全需要程序员自己实现。

若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断表示设置成true 接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断

每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断; 通过调用线程对象的interrupt方法将该线程的标识为设为true; 可以在别的线程中调用,也可以在自己的线程中调用。

  1. 通过volatile变量实现
  2. 通过AtomicBoolean实现
  3. 通过Thread类自带的中断api实例方法实现
代码语言:javascript
复制
 public static void main(String[] args)
    {
        Thread t1 = new Thread(() -> {
            while (true)
            {
                if(Thread.currentThread().isInterrupted())
                {
                    System.out.println(Thread.currentThread().getName()+"\t isInterrupted()被修改为true,程序停止");
                    break;
                }
                System.out.println("t1 -----hello interrupt api");
            }
        }, "t1");
        t1.start();

        System.out.println("-----t1的默认中断标志位:"+t1.isInterrupted());

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }

        //t2向t1发出协商,将t1的中断标志位设为true希望t1停下来
        new Thread(() -> {
            t1.interrupt();
        },"t2").start();
        //t1.interrupt();

    }

 static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
    private static void m2_atomicBoolean()
    {
        new Thread(() -> {
            while (true)
            {
                if(atomicBoolean.get())
                {
                    System.out.println(Thread.currentThread().getName()+"\t atomicBoolean被修改为true,程序停止");
                    break;
                }
                System.out.println("t1 -----hello atomicBoolean");
            }
        },"t1").start();

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            atomicBoolean.set(true);
        },"t2").start();
    }

static volatile boolean isStop = false;
    private static void m1_volatile()
    {
        new Thread(() -> {
            while (true)
            {
                if(isStop)
                {
                    System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序停止");
                    break;
                }
                System.out.println("t1 -----hello volatile");
            }
        },"t1").start();

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            isStop = true;
        },"t2").start();
    }

总结:当对一个线程,调用interrupt方法时: ① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true, 仅此而已。 被设置中断标志的线程将继续正常运行,不受影响。 所以,interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。

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

代码语言:javascript
复制
  public static void main(String[] args)
    {
        //实例方法interrupt()仅仅是设置线程的中断状态位设置为true,不会停止线程
        Thread t1 = new Thread(() -> {
            for (int i = 1; i <=300; i++)
            {
                System.out.println("-----: "+i);
            }
            System.out.println("t1线程调用interrupt()后的的中断标识02:"+Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        System.out.println("t1线程默认的中断标识:"+t1.isInterrupted());//false

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        t1.interrupt();//true
        System.out.println("t1线程调用interrupt()后的的中断标识01:"+t1.isInterrupted());//true

        try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("t1线程调用interrupt()后的的中断标识03:"+t1.isInterrupted());
        //????---false中断不活动的线程不会产生任何影响。
        //ps: 两秒后线程已经执行完
    }

中断协商案例深度解析

代码语言:javascript
复制
 public static void main(String[] args)
    {
        Thread t1 = new Thread(() -> {
            while (true)
            {
                if(Thread.currentThread().isInterrupted())
                {
                    System.out.println(Thread.currentThread().getName()+"\t " +
                            "中断标志位:"+Thread.currentThread().isInterrupted()+" 程序停止");
                    break;
                }

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                   // Thread.currentThread().interrupt();//没有它,程序不会停止,为什么要在异常处,再调用一次??
                    e.printStackTrace();
                }

                System.out.println("-----hello InterruptDemo3");
            }
        }, "t1");
        t1.start();

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> t1.interrupt(),"t2").start();
    }

/**
 * 1 中断标志位,默认false
 * 2 t2 ----> t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
 * 3 中断标志位true,正常情况,程序停止,^_^
 * 4 中断标志位true,异常情况,InterruptedException,将会把中断状态将被清除,并且将收到InterruptedException 。中断标志位false
 *    导致无限循环
 *
 * 5 在catch块中,需要再次给中断标志位设置为true,2次调用停止程序才OK
 */

官方描述

中断只是一种协商机制,修改中断标识位仅此而已,不是立刻stop打断

代码语言:javascript
复制
  public static void main(String[] args)
    {
        //测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,
        // 第二次再调用时中断状态已经被清除,将返回一个false。


        System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted()); //false
        System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());//false
        System.out.println("----1");
        Thread.currentThread().interrupt();// 中断标志位设置为true
        System.out.println("----2");
        System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());//true
        System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());//false

        LockSupport.park();

        Thread.interrupted();//静态方法

        Thread.currentThread().isInterrupted();//实例方法
    }

静态方法interrupted将会清楚中断状态(源码传入的ClearInterrupted为true)

实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)

总结

线程中断相关的方法:

public void interrupt(); interrupt()方法是一个实例方法 它通知目标线程中断,也仅仅是设置目标线程的中断标志位为true.

public boolean isInterrupted(); isInterrupted()方法也是一个实例方法 它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志

public static boolean interrupted(), Thread类的静态方法interrupted() 返回当前线程的中断状态真实值(boolean类型)后会将当前线程的中断状态设为false, 此方法调用之后会清除当前线程的中断标志位的状态(将中断标志位置为false了),返回当前值并清零置false

线程等待和唤醒

LockSupport是用来创建和其他同步类的基本线程阻塞原语

文档

LockSupport中的 park()unpark() 的作用分别是阻塞线程和解除被阻塞的线程

三种线程等待唤醒的方式

  1. 使用Object的wait()方法让线程等待,使用 Object中的notify()方法唤醒线程
  2. 使用JUC包中Condition的await方法让线程等待,使用signal()方法唤醒线程
  3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

Object

代码语言:javascript
复制
 private static void syncWaitNotify()
    {
        Object objectLock = new Object();

        new Thread(() -> {
            try { TimeUnit.SECONDS.sleep(1); } 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();
    }

wait()和notify()必须放在同步代码块或同步方法中,并且成对出现 必须现wait()在notify(),否则程序无法执行,无法唤醒


Condition

代码语言:javascript
复制
 private static void lockAwaitSignal()
    {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            try { TimeUnit.SECONDS.sleep(1); } 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();

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            lock.lock();
            try
            {
                condition.signal();
                System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
            }finally {
                lock.unlock();
            }
        },"t2").start();
    }

Condtion中的线程等待和唤醒方法,需要先获取锁 一定要先await再signal


LockSupport

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit) 但与Semaphores不同,许可证不会累积,最多只有一个

park()/park(Object blocker):阻塞,permit许可证默认没有不能方向,所以一开始调用park()方法当前线程就会阻塞,直到别的线程给当前线程发放permit,park方法才会被唤醒

unpark(Thread thread):唤醒,调用unpark(thread)方法后,就会将thread线程的许可证permit发放,会自动唤醒park线程,即之前阻塞中的LockSupport.park()方法会立即返回

代码语言:javascript
复制
 public static void main(String[] args)
    {
        Thread t1 = new Thread(() -> {
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName() + "\t ----come in"+System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t ----被唤醒"+System.currentTimeMillis());
        }, "t1");
        t1.start();

        //暂停几秒钟线程
        //try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
        },"t2").start();

    }

LockSupport 天生无锁块要求 之前错误的先唤醒后后等待,LockSupport照样支持,先unpark再park相当于提前有了通行证unpark,park时就没有拦截。park和unpark必须一一对应,因为许可证不会累积,最多只有一个

总结

LockSupport是一个线程阻塞工具类,所有的方法都是静态的,可以让线程在任意位置阻塞,阻塞之后也有对于的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码(native标识的方法即调用底层C++、C代码)。

LockSupport提供的park() 和 unpark()方法实现阻塞线程和解除线程阻塞的过程 LockSupport和每个使用它的线程都有一个许可(permit)关联。 每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会累加凭证。

理解

线程阻塞需要消耗凭证(permit),这个凭证最多只有一个。

当调用park方法时

  • 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
  • 如果无凭证,就必须阻塞等待凭证可用;

而unpark则相反,它会增加一个凭证,但凭证最多只能有一个,累加无效。

为什么可以突破wait/notify的原有调用顺序?

因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的靠凭证消费,故不会阻塞。 先发放了凭证后续可以畅通无阻。

为什么唤醒两次后阻塞两次,但最终结果还是会阻塞线程?

因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证; 而调用两次park却需要消费两个凭证,证不够不能放行。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 中断机制
  • 线程等待和唤醒
    • Object
    • Condition
    • LockSupport
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档