4. LockSupport与线程中断

news2024/9/29 13:21:07

4.1 线程中断机制
4.1.1 从阿里蚂蚁金服面试题讲起
Java.lang.Thread下的三个方法:

在这里插入图片描述
● 如何中断一个运行中的线程?
● 如何停止一个运行中的线程?

4.1.2 什么是中断机制
● 首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运,所以,Thread.stop,Thread.suspend,Thread.resume都已经被废弃了
● 其次,在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制----中断,也即中断标识协商机制

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

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

public void interrupt()
  ○ 实例方法 Just to set the interrupt flag
  ○ 实例方法仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程
● public static boolean interrupted()
  ○ 静态方法 Thread.interrupted();
  ○ 判断线程是否被中断并清除当前中断状态(做了两件事情)
    ■ 1.返回当前线程的中断状态,测试当前线程是否已被中断
    ■ 2.将当前线程的中断状态清零并重新设置为false,清除线程的中断状态
    ■ 3.这个方法有点不好理解在于如果连续两次调用此方法,则第二次返回false,因为连续调用两次的结果可能不一样
● public boolean isInterrupted()
  ○ 实例方法
  ○ 判断当前线程是否被中断(通过检查中断标志位)

.1.4 大厂面试题中断机制考点
● 如何停止中断运行中的线程?
○ 通过一个volatile变量实现


public class InterruptDemo {
    static volatile boolean isStop = false; //volatile表示的变量具有可见性

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println(Thread.currentThread().getName() + " isStop的值被改为true,t1程序停止");
                    break;
                }
                System.out.println("-----------hello volatile");
            }
        }, "t1").start();
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}
/**
 * -----------hello volatile
 * -----------hello volatile
 * -----------hello volatile
 * -----------hello volatile
 * -----------hello volatile
 * -----------hello volatile
 * t1 isStop的值被改为true,t1程序停止
 */

通过AutomicBoolean

public class InterruptDemo {
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);


    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.println(Thread.currentThread().getName() + " atomicBoolean的值被改为true,t1程序停止");
                    break;
                }
                System.out.println("-----------hello atomicBoolean");
            }
        }, "t1").start();
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    }
}

/**
 * -----------hello atomicBoolean
 * -----------hello atomicBoolean
 * -----------hello atomicBoolean
 * -----------hello atomicBoolean
 * -----------hello atomicBoolean
 * t1 atomicBoolean的值被改为true,t1程序停止
 */

通过Thread类自带的中断API实例方法实现----在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑stop线程。

public class InterruptDemo {
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);


    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " isInterrupted()的值被改为true,t1程序停止");
                    break;
                }
                System.out.println("-----------hello isInterrupted()");
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

        //当然,也可以t1自行设置
        t1.interrupt();

    }
}
/**
 * -----------hello isInterrupted()
 * -----------hello isInterrupted()
 * -----------hello isInterrupted()
 * -----------hello isInterrupted()
 * t1 isInterrupted()的值被改为true,t1程序停止
 */
● 当前线程的中断标识为true,是不是线程就立刻停止?
答案是不立刻停止,具体来说,当对一个线程,调用interrupt时:
  ○ 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,
  仅此而已,被设置中断标志的线程将继续正常运行,不受影响,
  所以interrupt()并不能真正的中断线程,
  需要被调用的线程自己进行配合才行,对于不活动的线程没有任何影响。
  ○ 如果线程处于阻塞状态(例如sleep,wait,join状态等),
  在别的线程中调用当前线程对象的interrupt方法,
  那么线程将立即退出被阻塞状态(interrupt状态也将被清除),
  并抛出一个InterruptedException异常。
/**
 * 执行interrupt方法将t1标志位设置为true后,t1没有中断,仍然完成了任务后再结束
 * 在2000毫秒后,t1已经结束称为不活动线程,设置状态为没有任何影响
 */
public class InterruptDemo2 {
    public static void main(String[] args) {
        //实例方法interrupt()仅仅是设置线程的中断状态位为true,不会停止线程
        Thread t1 = new Thread(() -> {
            for (int i = 1; i <= 300; i++) {
                System.out.println("------: " + i);
            }
            /**
             * ------: 298
             * ------: 299
             * ------: 300
             * t1线程调用interrupt()后的中断标志位02:true
             */
            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
        /**
         * ------: 251
         * ------: 252
         * ------: 253
         * t1线程调用interrupt()后的中断标志位01:true
         */
        System.out.println("t1线程调用interrupt()后的中断标志位01:" + t1.isInterrupted());//true

        try {
            TimeUnit.MILLISECONDS.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2000毫秒后,t1线程已经不活动了,不会产生任何影响
        System.out.println("t1线程调用interrupt()后的中断标志位03:" + t1.isInterrupted());//false

    }
}
/**
 *1. 中断标志位默认为false
 * 2.t2对t1发出中断协商  t1.interrupt();
 * 3. 中断标志位为true: 正常情况 程序停止
 *     中断标志位为true  异常情况,.InterruptedException ,将会把中断状态清楚,中断标志位为false
 * 4。需要在catch块中,再次调用interrupt()方法将中断标志位设置为false;
 */
public class InterruptDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " 中断标志位为:" + Thread.currentThread().isInterrupted() + " 程序停止");
                    break;
                }
                //sleep方法抛出InterruptedException后,中断标识也被清空置为false,如果没有在
                //catch方法中调用interrupt方法再次将中断标识置为true,这将导致无限循环了
                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();
    }
}

4.1.5 总结
● public void interrupt() 是一个实例方法,它通知目标线程中断,也仅仅是设置目标线程的中断标志位为true
● public boolean isInterrupted() 是一个实例方法,它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志
● public static boolean interrupted() 是一个静态方法,返回当前线程的中断真实状态(boolean类型)后会将当前线程的中断状态设为false,此方法调用之后会清楚当前线程的中断标志位的状态(将中断标志置为false了),返回当前值并清零置为false。

4.2 LockSupport是什么
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,其中park()和unpack()而作用分别是阻塞线程和解除阻塞线程.

4.3 线程等待唤醒机制

4.3.1 三种让线程等待和唤醒的方法

● 方式一:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程

● 方式二:使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程

● 方式三:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
4.3.2 Object类中的wait和notify方法实现线程等待和唤醒
● wait和notify方法必须要在同步代码块或者方法里面,且成对出现使用
● 先wait再notify才ok

Object类中的wait和notify方法实现线程等待和唤醒演示

public class LockSupportDemo {

    public static void main(String[] args) {
        Object objectLock = new Object();
        /**
         * t1	 -----------come in
         * t2	 -----------发出通知
         * t1	 -------被唤醒
         */
        new Thread(() -> {
            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();
    }
}

4.3.3 Condition接口中的await和signal方法实现线程的等待和唤醒
Condition中的线程等待和唤醒方法,需要先获取锁
一定要先await后signal,不要反了
Condition接口中的await和signal方法实现线程的等待和唤醒演示

public class LockSupportDemo {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        /**
         * t1	 -----------come in
         * t2	 -----------发出通知
         * t1	 -----------被唤醒
         */
        new Thread(() -> {
            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();

    }
}

4.3.4 上述两个对象Object和Condition使用的限制条件
● 线程需要先获得并持有锁,必须在锁块(synchronized或lock)中
● 必须要先等待后唤醒,线程才能够被唤醒

4.3.5 LockSupport类中的park等待和unpark唤醒
是什么
LockSupport 是用于创建锁和其他同步类的基本线程阻塞原语
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(Permit),许可证只能有一个,累加上限是1。
主要方法
阻塞: Peimit许可证默认没有不能放行,所以一开始调用park()方法当前线程会阻塞,直到别的线程给当前线程发放peimit,park方法才会被唤醒。
park/park(Object blocker)-------阻塞当前线程/阻塞传入的具体线程
唤醒: 调用unpack(thread)方法后 就会将thread线程的许可证peimit发放,会自动唤醒park线程,即之前阻塞中的LockSupport.park()方法会立即返回。
unpark(Thread thread)------唤醒处于阻塞状态的指定线程
代码
LockSupport类中的park等待和unpark唤醒演示

public class LockSupportDemo {

    public static void main(String[] args) {
        /**
         * t1	 -----------come in
         * t2	 ----------发出通知
         * t1	 ----------被唤醒
         */
        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();

        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时调用Unsafe中的native代码
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,LockSupport和每个使用它的线程都有一个许可(Peimit)关联,每个线程都有一个相关的permit,peimit最多只有一个,重复调用unpark也不会积累凭证。
形象理解:线程阻塞需要消耗凭证(Permit),这个凭证最多只有一个
当调用park时,如果有凭证,则会直接消耗掉这个凭证然后正常退出。如果没有凭证,则必须阻塞等待凭证可用;
当调用unpark时,它会增加一个凭证,但凭证最多只能有1各,累加无效。

面试题
为什么LockSupport可以突破wait/notify的原有调用顺序?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以畅通无阻。

为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证,而调用两次park却需要消费两个凭证,证不够,不能放行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/636680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

排水管网监测,万宾排水管网在线监测系统

城市排水管网是保障城市环境卫生的重要基础设施。然而&#xff0c;传统的排水管网管理方式存在许多问题.通过排水管网在线监测系统的建设&#xff0c;可以实现对管网上窨井井盖状态、管网液位、管网流量、管网水质等数据采集&#xff0c;实时掌握排水管网运行状况&#xff0c;为…

@足智多谋的你,联网智能门锁试卷(2023高考版)正式发布!

千淘万漉虽辛苦&#xff0c;吹尽狂沙始到金。2023年的高考已于上周正式结束。湖南省高考成绩和录取控制分数线将在6月25日正式公布。广大学子历经一千多个日夜的努力&#xff0c;即将在这一天见证结果。 其实&#xff0c;当我们真正走出校园才发现&#xff0c;学无止境&#x…

敏捷项目管理

在了解敏捷项目管理之前&#xff0c;我们先看下敏捷和传统项目管理有什么区别。 传统项目管理&#xff1a;阶段式项目管理模式。 制定详细的计划和步骤&#xff0c;按计划执行&#xff0c;直到所有的计划执行全部结束。 敏捷项目管理模式&#xff0c;从愿景和高价值的目标出发…

V5.0.X版本 EMQX安装、卸载 以及使用

V5.0.X版本 EMQX安装、卸载 以及使用 一、卸载二、下载安装2.1 下载2.2 安装2.2.1 rpm安装2.2.2 tar安装 2.3 测试启动 三、EMQX使用 回到目录    回到末尾 EMQX为大规模分布式物联网 MQTT 消息服务器。提供高效可靠连接海量物联网设备&#xff0c;实时处理分发消息与事件流数…

高级信息系统项目管理师资料分享

&#x1f495;&#x1f495; 推荐&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; 文章目录 1. 高级信息系统项目管理师资料分享2023年5月12日2023年4月10日2023年3月28日2023.5高项精讲 2023备考高级信息系统项目管理师资料 1. 高级信息系统项目管理师资料分…

超级实用!Python 3.6帮您提升编码效率的一个新的小特性 ——f{}

Python3.6 格式化字符串的新用法 在python 3.6格式化字符串时&#xff0c;增加了一个非常实用的新特性&#xff0c;文本以“f”或者“F”为前缀加上{}&#xff0c;可以实现类似于%和str.format的功能&#xff0c;但与他们相比更加易读和不易不错。使用也非常方便&#xff0c;以…

3分钟教你怎样搭建属于你的私有仓库

上一节我们讲了实战项目&#xff1a;docker部署springboot项目&#xff0c;我介绍了镜像的基本操作和镜像的原理以及如何利用docker部署springboot项目&#xff0c;那么有了镜像&#xff0c;我们应该如何更好地存储和分发镜像呢&#xff1f;答案就是今天的主角——Docker 的镜像…

如何能够系统的自学网络安全(黑客)?

一、自学网络安全学习的误区和陷阱 1.不要试图以编程为基础的学习开始学习 我在之前的回答中&#xff0c;我都一再强调不要以编程为基础再开始学习网络安全&#xff0c;一般来说&#xff0c;学习编程不但学习周期长&#xff0c;而且实际向安全过渡后可用到的关键知识并不多 一…

ResultMap使用

要解决的问题&#xff1a;属性名和字段名不一致 1.1 查询为null问题 查看之前的数据库的字段名 Java中的实体类设计 public class User { private int id; //id private String name; //姓名 private String password; //密码和数据库不一样&#xff01; //构造 //set/get /…

容器(第六篇)docker-harbor

什么是Harbor Harbor 是 VMware 公司开源的企业级 Docker Registry 项目&#xff0c;其目标是帮助用户迅速搭建一个企业级的 Docker Registry 服务。 Harbor以 Docker 公司开源的 Registry 为基础&#xff0c;提供了图形管理 UI 、基于角色的访问控制(Role Based AccessContr…

常用的decap MOS电容版图介绍

常用的两种decap版图如下&#xff1a; 图a 图b 很多人认为decap电容就是source drain 衬底接地做下极板&#xff0c;poly gate接电源做上极板&#xff0c;这种认同是错误的。 我们先把MOS电容拆分成pmos和nmos去看。对于图a&#xff0c;poly没有接任何东西&#xff0c;pmos部…

数据库事务隔离级别

数据库事务隔离级别&#xff1a; 不同隔离级别引发的问题&#xff1a;对于同时运行的多个事务&#xff08;多线程并发&#xff09;, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题: &#xff08;问题的本质就是线程安全问题&#x…

2核4G云服务器可以支持多少个网站数量?多维度权衡

2核4G服务器可以安装多少个网站&#xff1f;阿腾云2核4G5M带宽服务器目前安装了14个网站&#xff0c;从技术角度是没有限制的&#xff0c;只要云服务器性能够用&#xff0c;想安装几个网站就安装几个网站&#xff0c;但是从公网带宽和CPU计算性能方面考虑&#xff0c;要计算网站…

6.10 线程池及gdb调试多线程

线程池概念和使用 概念&#xff1a; 通俗的讲就是一个线程的池子&#xff0c;可以循环的完成任务的一组线程集合 必要性&#xff1a; 我们平时创建一个线程&#xff0c;完成某一个任务&#xff0c;等待线程的退出。但当需要创建大量的线程时&#xff0c;假设T1为创建线程时间&…

DAY 74 ELK企业级日志分析系统

ELK 简介 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kiabana 三个开源工具配合使用&#xff0c; 完成更强大的用户对日志的查询、排序、统计需求 ELK各组件介绍 ElasticSearch&#xff1a; 是基于Lucene&#xff08;一个全文检索引…

大麦一键生成订单截图 大麦生成购票链接

一键生成订单截图&#xff0c;生成购票链接 已对接支付 下载程序&#xff1a;https://pan.baidu.com/s/16lN3gvRIZm7pqhvVMYYecQ?pwd6zw3

idea设置@Author文件头注释

背景&#xff1a;做项目开发&#xff0c;每个文件的文件头现在看着特别难受&#xff0c;想着弄着统一一些吧 第一步&#xff1a;选择intelliJ IDEA&#xff0c;然后选择settings 第二步&#xff1a;选择Editor,再选择File and Code Templates 第三步&#xff1a;选择Includes,…

API管理的正确姿势--API Gateway

淘宝API管理的正确姿势--API Gateway 编者按&#xff1a; 数字化生态&#xff0c;以创新客户体验为核心&#xff0c;所有我们身边能感知到的变化都来自于渐近的创新。这些创新需要试错&#xff0c;需要不断的升级&#xff0c;并且创新往往与我们熟知的功能分离开来分别呈现。微…

企业级信息系统开发讲课笔记4.9 Thymeleaf模板引擎

文章目录 零、学习目标一、Spring Boot支持的视图技术二、Thymeleaf基本语法1、Thymeleaf常用标签2、Thymeleaf主要语法3、Thymeleaf内置对象4、Thymeleaf模板基本配置 三、Spring Boot整合Thymeleaf1、创建Spring Boot项目ThymeleafDemo2、在全局配置文件里配置Thymeleaf属性3…

简化本地Feign调用,老手教你这么玩

原创&#xff1a;微信公众号 码农参上&#xff0c;欢迎分享&#xff0c;转载请在文章头部保留出处&#xff0c;侵权必究 哈喽大家好啊&#xff0c;我是Hydra。 在平常的工作中&#xff0c;OpenFeign作为微服务间的调用组件使用的非常普遍&#xff0c;接口配合注解的调用方式突…