【JUC】二、线程间的通信(虚假唤醒)

news2024/11/16 15:44:55

文章目录

  • 0、多线程编程的步骤
  • 1、wait和notify
  • 2、synchronized下实现线程的通信(唤醒)
  • 3、虚假唤醒
  • 4、Lock下实现线程的通信(唤醒)
  • 5、线程间的定制化通信

0、多线程编程的步骤

  • 步骤一:创建(将来被共享的)资源类,创建属性和操作方法
  • 步骤二:在资源类的操作方法中进行:判断、干活儿、通知
  • 步骤三:创建多线程调用资源类的方法
  • 步骤四:防止虚假唤醒现象

1、wait和notify

  • wait 和notify方法是任何一个Java对象都有的方法,因为这两个方法是Object类的方法
  • wait方法让正在o对象上活动的线程进入等待状态,并释放o对象的对象锁,直到被唤醒为止
Object o = new Object();
o.wait();
//public final void wait(long timeout) 
//timeout是要等待的最长时间
  • 注意是正在o对象上活动的线程,而不是线程对象,所以别t.wait
  • notify方法,唤醒在此对象上等待的单个线程,如果此对象上有多个线程在等待,那就随机唤醒一个线程直到当前线程放弃此对象上的锁定,这个被唤醒的线程才能继续执行,且如果该对象上还有其他主动同步的线程,则被唤醒的线程要与它们进行竞争,如果该对象上就这一个线程(刚被唤醒的线程),那就自然是它抢到对象锁
  • notifyAll,即唤醒该对象上等待的所有线程,和notify一样,也是等当前线程释放占用的对象锁后,再竞争、执行

Object的wait()与Thread.sleep()的区别:

  • Thread.sleep()是让当前线程 拿着锁 睡眠指定时间,时间一到手里拿着锁自动醒来,还可以接着往下执行
  • Object的wait则是 睡前释放锁 ,只有当前锁对象调用了notify或者notifyAll方法才会醒来(不考虑wait带参数指定睡眠时间),且醒来手里也没锁,得再竞争,也就是说wait的线程被唤醒是就绪状态

2、synchronized下实现线程的通信(唤醒)

案例:启动两个线程,实现对同一个数交替的加1和减1操作

//资源类
class Share{
    //初始值
    private int number = 0;
    
    //+1
    public synchronized void incr() throws InterruptedException {
        if(number != 0){
            this.wait();   //让share对象上的线程等待
        }
        number++;
        System.out.println(Thread.currentThread().getName() + " ==> " + number);
        //通知其他线程
        this.notify();
        
    }
    
    //-1
    public synchronized  void decr() throws InterruptedException {
        if(number != 1){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " ==> " + number);
        this.notify();
    }
}

启动多线程,共用资源类对象,调用资源类的方法:

public class ThreadMessage {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(() -> {
            for(int i = 0; i <= 10 ; i++){
                try {
                    share.incr();   //+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(() -> {
            for(int i = 0; i <= 10 ; i++){
                try {
                    share.decr();  //-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
        
    }
    
}

在这里插入图片描述

3、虚假唤醒

上面的代码改为4个线程对同一个数字进行操作,其中AA、CC负责加一,BB、DD负责减一:

public class ThreadMessage {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(() -> {
            for(int i = 0; i <= 10 ; i++){
                try {
                    share.incr();   //+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(() -> {
            for(int i = 0; i <= 10 ; i++){
                try {
                    share.decr();  //-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(() -> {
            for(int i = 0; i <= 10 ; i++){
                try {
                    share.incr();   //+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();

        new Thread(() -> {
            for(int i = 0; i <= 10 ; i++){
                try {
                    share.decr();  //-1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();

    }

}

执行发现有非0非1的数值出现:

在这里插入图片描述

发生这个错误的原因是,wait方法的特点是在哪一行睡,就在哪一行醒,然后接着往下执行,这就导致了wait前面的判断条件只有第一次生效,这就是虚假唤醒。画个草图:

在这里插入图片描述

注意,这里可能还有一个因唤醒不当而导致阻塞的情况,多次运行会出现:光标闪烁,但没有输出,也没有exit 0。这个后面篇章再整理。

在这里插入图片描述

对于虚假唤醒的解决办法就是把if换成while,即在循环中使用,此时睡醒接着往下执行也会先判断一下

//资源类
class Share{
    //初始值
    private int number = 0;
    
    //+1
    public synchronized void incr() throws InterruptedException {
        while(number != 0){  //改为while
            this.wait();   //让share对象上的线程等待
        }
        number++;
        System.out.println(Thread.currentThread().getName() + " ==> " + number);
        //通知其他线程
        this.notify();
        
    }
    
    //-1
    public synchronized  void decr() throws InterruptedException {
        while(number != 1){  //改为while
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " ==> " + number);
        this.notify();
    }
}

虚假唤醒不再出现:

在这里插入图片描述

结论:wait方法可能出现虚假唤醒,应该在循环中调用wait方法

synchronized (obj) {

	while (<condition does not hold>){
	
		obj.wait(timeout);
		... // Perform action appropriate to condition
	}
}

4、Lock下实现线程的通信(唤醒)

Lock代替了synchronized,Condition接口替代Object类的wait、notify等监视器方法。Lock接口下的重要方法:

  • newCondition():返回绑定到此 Lock 实例的新 Condition 实例
  • await方法类比Object的wait
  • signal():唤醒一个等待线程
  • signalAll():唤醒所有等待线程

接下来用Lock接口实现上面synchronized的案例,对同一个数字进行加减1:

class ShareObj{

    private int number = 0;

    //创建Lock,用可重复锁的实现类
    Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void incr() throws InterruptedException {
        //上锁
        lock.lock();

        try {
            //判断
            while(number != 0){
                condition.await();;
            }
            //干活儿
            number++;
            System.out.println(Thread.currentThread().getName() + " ==> " + number);
            //通知
            condition.signalAll();
        } finally {
            //解锁
            lock.unlock();
        }
    }

    public void decr() throws InterruptedException {
        lock.lock();
        try {
            //判断
            while(number != 1){
                condition.await();;
            }
            //干活儿
            number--;
            System.out.println(Thread.currentThread().getName() + " ==> " + number);
            //通知
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

启动多线程调用资源类方法:

public class ThreadDemo {

    public static void main(String[] args) {

        ShareObj shareObj = new ShareObj();

        new Thread(() -> {
            for(int i = 0; i <= 10; i++ ){
                try {
                    shareObj.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();
        new Thread(() -> {
            for(int i = 0; i <= 10; i++ ){
                try {
                    shareObj.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
        new Thread(() -> {
            for(int i = 0; i <= 10; i++ ){
                try {
                    shareObj.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
        new Thread(() -> {
            for(int i = 0; i <= 10; i++ ){
                try {
                    shareObj.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();
    }
}

在这里插入图片描述

5、线程间的定制化通信

可以看到,前面的例子中,AA线程执行完后,共享的那把对象锁被谁抢到,或者说接下来是哪个线程执行,这一点是随机的。这里要实现的就是将这个通信变成定制化的。案例:

启动三个线程,按照如下顺序运行:

- AA线程打印5次,BB打印10次,CC打印15- ...
- 进行10轮这个打印

实现思路是给每一个线程引入一个标志位flag变量:

在这里插入图片描述

代码实现:

//定义资源类
class ShareSource {

    //定义标志位,AA线程对应1,BB对应2,CC对应3
    private Integer flag = 1;
    //创建Lock锁
    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    /**
     * 打印5次
     * @param loop 第几轮
     */
    public void print5(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            //判断
            while(flag != 1){
                c1.await();
            }
            //干活,fori快捷生成
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "==>" + i + ",第"+ loop + "轮");
            }
            //改标志位并通知BB
            flag = 2;
            c2.signal();
        }finally {
           //解锁
           lock.unlock();
        }
    }

    /**
     * 打印10次
     * @param loop 第几轮
     */
    public void print10(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            //判断
            while(flag != 2){
                c2.await();
            }
            //干活fori快捷生成
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "==>" + i + ",第"+ loop + "轮");
            }
            //改标志位并通知CC
            flag = 3;
            c3.signal();
        }finally {
            //解锁
            lock.unlock();
        }
    }

    /**
     * 打印15次
     * @param loop 第几轮
     */
    public void print15(int loop) throws InterruptedException {
        //上锁
        lock.lock();
        try{
            //判断
            while(flag != 3){
                c3.await();
            }
            //干活fori快捷生成
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "==>" + i + ",第"+ loop + "轮");
            }
            //改标志位并通知AA
            flag = 1;
            c1.signal();
        }finally {
            //解锁
            lock.unlock();
        }
    }
}

启动多线程,调用资源类中的方法:

public class ThreadDemoTwo {
    public static void main(String[] args) {
        ShareSource shareSource = new ShareSource();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    shareSource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    shareSource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    shareSource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
    }
}

运行:

在这里插入图片描述

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

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

相关文章

FD-Align论文阅读

FD-Align: Feature Discrimination Alignment for Fine-tuning Pre-Trained Models in Few-Shot Learning&#xff08;NeurIPS 2023&#xff09; 主要工作是针对微调的和之前的prompt tuining&#xff0c;adapter系列对比 Motivation&#xff1a; 通过模型对虚假关联性的鲁棒…

联想小新Pro14默认设置的问题

联想小新Pro14 锐龙版&#xff0c;Win11真的挺多不习惯的&#xff0c;默认配置都不符合一般使用习惯。 1、默认人走过自动开机。人机互动太强了&#xff1b; 2、默认短超时息屏但不锁屏&#xff0c;这体验很容易觉得卡机然后唤起&#xff0c;却又不用密码打开&#xff1b; 3…

(头哥)多表查询与子查询

目录 第1关&#xff1a;查询每个学生的选修的课程信息 第2关&#xff1a;查询选修了“数据结构”课程的学生名单 第3关&#xff1a;查询“数据结构”课程的学生成绩单 第4关&#xff1a;查询每门课程的选课人数 第5关&#xff1a;查询没有选课的学生信息 第6关&#xff1a…

Linux下C++调用python脚本实现LDAP协议通过TNLM认证连接到AD服务器

1.前言 首先要实现这个功能&#xff0c;必须先搞懂如何通过C调用python脚本文件最为关键&#xff0c;因为两者的环境不同。本质上是在 c 中启动了一个 python 解释器&#xff0c;由解释器对 python 相关的代码进行执行&#xff0c;执行完毕后释放资源。 2 模块功能 2.1python…

设计模式1

![在这里插入图片描述](https://img-blog.csdnimg.cn/c9fbecf1ae89436095885722380ea460.png)一、设计模式分类&#xff1a; 1、创建型模式&#xff1a;创建与使用分离&#xff0c;单例、原型、工厂、抽象、建造者。 2、结构型模式&#xff1a;用于描述如何将对象按某种更大的…

01-Spring中的工厂模式

工厂模式 工厂模式的三种形态: 工厂模式是解决对象创建问题的属于创建型设计模式,Spring框架底层使用了大量的工厂模式 第一种&#xff1a;简单工厂模式是工厂方法模式的一种特殊实现,简单工厂模式又叫静态工厂方法模式不属于23种设计模式之一第二种&#xff1a;工厂方法模式…

Leetcode—234.回文链表【简单】

2023每日刷题&#xff08;二十七&#xff09; Leetcode—234.回文链表 直接法实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ bool isPalindrome(struct ListNode* head) {if(head NULL) {return t…

【mysql】CommunicationsException: Communications link failure

CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. 通信异常&#xff1a;通信链路故障 最后一个成功发送到服务器的数据包是0毫秒前…

华为ensp:ospf末梢stub完全末梢totally Stub

现在宣告都宣告完了&#xff0c;现在要给area1做完全末梢 末梢区域 进入r2系统视图模式 ospf 1area 1 stub quit进入r1系统视图 ospf 1 area 1 stub quit 现在去r1上查看 末梢成功 完全末梢 进入r2系统视图 ospf 1 area 1stub no-summary 现在就成为完全末梢了&…

YOLOv8-Seg改进: 分割小目标系列篇 | 小目标分割到大目标分割一网打尽的GFPN

🚀🚀🚀本文改进:一种新的路径融合GFPN,包含跳层与跨尺度连接,助力YOLOv8-seg Neck,实现创新 🚀🚀🚀GFPN在不同分割场景均有涨点的表现,尤其适合存在不同大小分割尺寸的场景 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,…

计算机毕业设计选题推荐-校园交流平台微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

1204. 错误票据

题目&#xff1a; 1204. 错误票据 - AcWing题库 思路&#xff1a; 将输入的数据存入数组&#xff0c;从小到大排序后遍历&#xff0c;若 (a[i] a[i - 1])res1 a[i]--->重号;若(a[i] - a[i - 1] > 2)res2 a[i] - 1--->断号。 难点&#xff1a;题目只告诉我们输入…

【Linux】:静动态库

静动态库 一.静态库1.设计静态库2.生成静态库3.发布静态库4.使用静态库 二.动态库1.设计动态库2.生成和发布动态库3.使用 一.静态库 程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。 静态库链接格式&#xff1a;libxxx.a(前缀是lib,后缀是…

【数字图像处理】RGB 转灰度图

常见的数字图像格式有 RGB, RGBA, YCbCr 等&#xff0c;RGB/RGBA 格式适合存储&#xff0c;而 YCbCr 格式适合图像处理。在数字图像处理中&#xff0c;通常需要将 RGB 格式的图像变换为灰度图&#xff0c;再进行后续的处理&#xff0c;例如边缘检测、锐化等。本文主要介绍数字图…

数据结构 树和二叉树

敬请期待 1. 术语详解 树&#xff1a; 二叉树&#xff1a; 森林&#xff1a; 完全二叉树&#xff1a; 满二叉树&#xff1a; 二叉排序树&#xff1a; 二叉搜索树&#xff1a; 哈夫曼树&#xff1a;分为左小右大和左先右后两种构造方法。 平衡二叉树&#xff1a; 线索…

【Bug】当用opencv库的imread()函数读取图像,用matplotlib库的plt.imshow()函数显示图像时,图像色彩出现偏差问题的解决方法

一&#xff0c;问题描述 我们在利用opencv的imread读取本地图像&#xff0c;进行一系列处理&#xff0c;但是发现用matplotlib库的imshow&#xff08;&#xff09;函数显示的时候出现色彩改变&#xff0c;比如图像偏黄&#xff0c;偏红&#xff0c;偏蓝等等&#xff0c;但是对…

黑马程序员微服务Docker实用篇

Docker实用篇 0.学习目标 1.初识Docker 1.1.什么是Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署…

消息队列之初识Rabbit及安装

文章目录 一、MQ的相关概念1.什么是MQ&#xff1f;2.为什么要用MQ2.1流量消峰2.2应用解耦2.3异步处理 3.MQ 的分类3.1.ActiveMQ3.2.Kafka3.3.RocketMQ3.4.RabbitMQ 4.MQ 的选择4.1.Kafka4.2.RocketMQ4.3.RabbitMQ 二、RabbitMQ的相关概念1.四大核心概念2.RabbitMQ 核心部分3.Ra…

面向萌新的技术博客入门指南

Python之禅 在Python的解释器中隐藏一个彩蛋&#xff0c;输入import this就会返回19条Python之禅&#xff0c;具体如下&#xff1a; import this The Zen of Python, by Tim Peters Python之禅 &#xff0c;by Tim Peters Beautiful is better than ugly. 优美好于丑陋&…

JS算法练习 11.12

leetcode 2622 有时间限制的缓存 看这道题之前&#xff0c;先复习一下Map类的用法&#xff08;和array.map()区分开&#xff09; //创建一个Map对象 const map new Map();//set()方法添加键值对 map.set(key, value); map.set(key, {value1, value2})//get()获取键对应的值 …