Java多线程的3种等待唤醒机制

Java多线程 3 种等待、唤醒机制

  • Object类中的wait、notify方法配置synchronized关键字;
  • Condition接口中的await、signal方法配合Lock;
  • LockSupport类中的park、unpark方法(无需锁

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

注意

  • 不能脱离synchronized代码块使用,否则会抛出IllegalMonitorStateException异常;
  • 先wait后notify、notifyAll,等待中的线程才能被唤醒,顺序不能改变
public class LockDemo {static Object object = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>线程Come in");try {// 使当前线程等待object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}}, "A").start();new Thread(() -> {synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒object.notifyAll();}}, "B").start();}
}

我们来看下上面代码的执行结果,看起来很和谐:

A==>线程Come in
B==>通知线程
A==>线程被唤醒

我们来将上面代码动下手脚,注释掉10、23代码看下wait/notify脱离了Synchronized会出现什么?
可以看到报了异常,说明wait/notify不能够脱离Synchronized代码块

public class LockDemo {static Object object = new Object();public static void main(String[] args) {new Thread(() -> {
//            synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>线程Come in");try {// 使当前线程等待object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");
//            }}, "A").start();new Thread(() -> {
//            synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒object.notifyAll();
//            }}, "B").start();}
}

我们再来看一下,线程A先sleep 2秒,让线程B先执行notify方法,会发生什么?

public class LockDemo {static Object object = new Object();public static void main(String[] args) {new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>线程Come in");try {// 使当前线程等待object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}}, "A").start();new Thread(() -> {synchronized (object) {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒object.notifyAll();}}, "B").start();}
}

 线程A由于没有被唤醒,阻塞住了

二、Condition接口中的await和single方法实现线程的等待和唤醒

注意

  • 必须配合lock()方法使用,否则抛出IllegalMonitorStateException异常
  • 等待唤醒调用顺序不能改变
public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>线程Come in");// 使当前线程等待condition.await();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}, "A").start();new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒condition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}, "B").start();}
}

看下上面代码的执行结果:

A==>线程Come in
B==>通知线程
A==>线程被唤醒

我们来将上面代码动下手脚,注释掉9 17 24 32行代码看下wait/notify脱离了Synchronized会出现什么?
可以看到报了异常,说明wait/notify不能够脱离Synchronized代码块

public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {
//            lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>线程Come in");// 使当前线程等待condition.await();} catch (InterruptedException e) {e.printStackTrace();} finally {
//                lock.unlock();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}, "A").start();new Thread(() -> {//            lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒condition.signal();} catch (Exception e) {e.printStackTrace();} finally {
//                lock.unlock();}}, "B").start();}
}

我们再来看一下使线程A暂停三秒会发生什么?
线程B先执行了,没有代码将线程A的await状态进行唤醒

public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>线程Come in");// 使当前线程等待condition.await();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}System.out.println(Thread.currentThread().getName() + "==>线程被唤醒");}, "A").start();new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + "==>通知线程");// 使当前线程唤醒condition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}, "B").start();}
}

同样,线程A会被阻塞

三、LockSupport类中的park等待和unpark唤醒

        LockSupport提供park()和unpark()方法实现阻塞和唤醒线程的过程。
        LockSupport和每一个使用它的线程之间有一个许可(permit)进行关联,permit有0和1两种状态,默认为0,即无许可证状态。
        调用一次unpark方法,permit加1变成1。每次调用park方法都会检查许可证状态,如果为1,则消耗掉permit(1 -> 0)并立刻返回;如果为0,则进入阻塞状态。permit最多只有一个,重复调用unpark也不会累积permit。

public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {Thread a = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "==>Come in");LockSupport.park();//阻塞当前线程System.out.println(Thread.currentThread().getName() + "==>被唤醒");});a.setName("A");a.start();new Thread(() -> {LockSupport.unpark(a);System.out.println(Thread.currentThread().getName() + "==>通知");}, "B").start();}
}

看下上面代码的执行结果:

A==>Come in
B==>通知
A==>被唤醒

下面给线程A进行等待3S,看会不会像一,二两个例子出现错误情况:
得出结论是:
  因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。

public class LockDemo {static Object object = new Object();// 创建可重入锁static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) {Thread a = new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "==>Come in");LockSupport.park();//阻塞当前线程System.out.println(Thread.currentThread().getName() + "==>被唤醒");});a.setName("A");a.start();new Thread(() -> {LockSupport.unpark(a);System.out.println(Thread.currentThread().getName() + "==>通知");}, "B").start();}
}

执行结果,不阻塞

        LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
        LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
 
        LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit,也就是将1变成o,同时park立即返回。如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
 
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时
    *如果有凭证,则会直接消耗掉这个凭证然后正常退出;
    *如果无凭证,就必须阻塞等待凭证可用;
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阻容降压电阻应用

公式:Xc1/2πfC 电流:IU/Xc 举例:1uf金属化聚丙烯膜电容的容抗是3184欧姆。电流是70ma。 实际应用中根据工作电流去倒推算电容。

vue Sts认证后直传图片到阿里云OSS

后端进行sts认证生成临时身份凭证,前端通过凭证直传图片等文件到OSS中 一 OSS配置 增加用户和角色,创建OSS bucket 1.1 添加用户 登录阿里云管理控制台,右侧头像,进入访问控制 点击左侧导航栏的身份管理的用户,点击…

景联文科技助力金融机构强化身份验证,提供高质量人像采集服务

随着社会的数字化和智能化进程的加速,人像采集在金融机构身份认证领域中发挥重要作用,为人们的生活带来更多便利和安全保障。 金融机构在身份验证上的痛点主要包括以下方面: 身份盗用和欺诈风险:传统身份验证方式可能存在漏洞&am…

基于ssm的大学生社团管理系统

基于ssm的大学生社团管理系统 摘要 基于SSM的大学生社团管理系统是一个全面、高效的社团管理平台,旨在帮助大学生和社团管理员更方便、更快捷地进行社团活动的组织和管理。该系统基于Spring、SpringMVC和MyBatis(简称SSM)开发,这三…

理解RNN以及模型搭建代码

RNN结构 这是一张不直观易懂的RNN结构示意图。但也是大家见得最多结构示意图。 RNN模型解释 RNN一文就讲解清楚的博客,看这里:https://zhuanlan.zhihu.com/p/408998328 RNN为什么梯度消失和梯度爆炸,看这里:https://zhuanlan.z…

Unity Mirror学习(三)ClientRpc特性使用

ClientRpc特性 1,从服务端任意一网络对象发送到客户端 2,修饰方法的,在服务器上调用此方法,它将在所有客户端执行(我的理解:服务端广播消息,消息方法) 3,此方法不会在本地执行 它和…

计算机毕业设计 基于SpringBoot的私人西服定制系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…

黑豹程序员-架构师学习路线图-百科:Knife4j API接口文档管理

文章目录 由来:接口文档第一代:Swagger第二代:Knife4j界面 由来:接口文档 古老编程是一个语言前后端通吃,ASP、JSP、PHP都是如此。 但随着项目规模变大,项目团队也开始壮大,岗位职责开始细分&a…

HTML的初步学习

HTML HTML 描述网页的骨架, 标签化的语言. HTML 的执行是浏览器的工作,浏览器会解析 html 的内容,根据里面的代码,往页面上放东西,浏览器的工作归根结底,还是以汇编的形式在CPU上执行. 浏览器对于html语法格式的检查没有很严格,即使你写的代码有一些不合规范之处,浏览器也会尽可…

Avalonia播放视频(mp4)

1.Nuget添加类库Dove.Avalonia.Extensions.Media,项目路径https://github.com/michael-eddy/Avalonia.Extensions/ 2.Nuget添加VideoLAN.LibVLC.Windows PlatformLibVLC PackageMinimum OS VersionWindowsVideoLAN.LibVLC.WindowsWindows XPUWPVideoLAN.LibVLC.UW…

华为云交换数据空间 EDS:“可信、可控、可证”能力实现你的数据你做主

文章目录 前言一、数据安全流通价值的必要性和紧迫性1.1、交换数据空间(EDS)背景1.2、《数字中国建设整体布局规划》1.3、数据流通成为制约数据要素价值释放的瓶颈 二、华为云 EDS 解决方案介绍2.1、构建可控数据交换空间2.2、可控的数据交换框架2.3、定…

聊一聊 tcp/ip 在.NET故障分析的重要性

一:背景 1. 讲故事 这段时间分析了几个和网络故障有关的.NET程序之后,真的越来越体会到计算机基础课的重要,比如 计算机网络 课,如果没有对 tcpip协议 的深刻理解,解决这些问题真的很难,因为你只能在高层…