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

文章目录

  • 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();
    }
}

运行:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c primer plus_chapter_four——字符串和格式化输入/输出

1、strlen&#xff08;&#xff09;&#xff1b;const&#xff1b;字符串&#xff1b;用c预处理指令#define和ANSIC的const修饰符创建符号常量&#xff1b; 2、c语言没有专门储存字符串的变量类型&#xff0c;字符串被储存在char类型的数组中&#xff1b;\0标记字符串的结束&a…

低价寄快递寄件微信小程序 实际商用版 寄快递 低价寄快递小程序(源代码+截图)前后台源码

盈利模式 快递代下CPS就是用户通过线上的渠道&#xff08;快递小程序&#xff09;&#xff0c;线上下单寄快递来赚取差价&#xff0c;例如你的成本价是5元&#xff0c;你在后台比例设置里面设置 首重利润是1元&#xff0c;续重0.5元&#xff0c;用户下1kg的单页面显示的就是6元…

LiteVNA 能做什么?

最近入手了一台 LiteVNA 设备&#xff0c;性价比非常高。因为之前没有接触过 VNA 这种测试仪器&#xff0c;所以准备好好研究一下。和它类似的一个项目是 NanoVNA6000&#xff0c;价格要高些&#xff0c;但可能性能要好点&#xff0c;另外&#xff0c;文档也要全一些。 VNA …

C++跨DLL内存所有权问题探幽(一)DLL提供的全局单例模式

最近在开发的时候&#xff0c;特别是遇到关于跨DLL申请对象、指针、内存等问题的时候遇到了这么一个问题。 问题 跨DLL能不能调用到DLL中提供的单例&#xff1f; 问题比较简单&#xff0c;就是我现在有一个进程A&#xff0c;有DLL B DLL C&#xff0c;这两个DLL都依赖DLL D的…

Linux系统编程——修改配置文件(应用)

该应用主要调用到strstr函数&#xff0c;我们只需调用该函数并传入相关文件和修改数值即可&#xff0c;下面就是对strstr函数的定义解读以及实现案例 1.调用strstr函数需要包含以下头文件 #include<string.h>2.函数定义格式 char *strstr(char *str1, const char *str…

深度学习4:BatchNormalization(批规范化)

一、起源 训练深度网络的时候经常发生训练困难的问题&#xff0c;因为&#xff0c;每一次参数迭代更新后&#xff0c;上一层网络的输出数据经过这一层网络计算后&#xff0c;数据的分布会发生变化&#xff0c;为下一层网络的学习带来困难。 Batch Normalizatoin 之前的解决方…

c语言:解决数组中数组缺少单个的元素的问题

题目&#xff1a;数组nums包含从0到n的所以整数&#xff0c;但其中缺了一个。请编写代码找出那个缺失的整数。O(n)时间内完成。 如&#xff0c;输入&#xff1a;【3&#xff0c;0&#xff0c;1】。 输出&#xff1a; 2 三种方法 &#xff1a; 方法1&#xff1a;排序&#xf…

使用.net 构建 Elsa Workflow

对接过蓝凌OA 也基于泛微OA数据库原型重新研发上线过产品&#xff0c;自研的开源的也上线过 每个公司对OA流程引擎介绍 都不一样的&#xff0c; 比如Elsa 这款微软MVP开源组件&#xff0c;基于跨平台开发的技术含量高&#xff0c;专门做OA的同行推过对应文章。 直接看怎么用吧。…

angular学习笔记

HTML绑定 形式&#xff1a;{{ 变量名 }} {{}}内部可以是 算数运算比较运算逻辑运算三目运算调用函数 {{}}内部不可以是 创建对象&#xff1a;不可以newJSON序列化 属性绑定 形式1&#xff1a;[属性名]“变量名” 形式2&#xff1a;属性名“{{变量名}}” <div [title…

C++ Qt 学习(六):Qt http 编程

1. http 基础 HTTP 基础教程C Web 框架 drogonoatpp 2. C Qt 用户登录、注册功能实现 login_register.h #pragma once#include <QtWidgets/QDialog> #include "ui_login_register.h" #include <QNetworkReply>class login_register : public QDialog…

信息系统项目管理师 教材目录、考试大纲、考情

文章目录 考情考试大纲第1章 信息化发展第2章 信息技术发展第3章 信息系统治理第4章 信息系统管理第5章 信息系统工程第6章 项目管理概论第7章 项目立项管理第8章 项目整合管理第9章 项目范围管理272第10章 项目进度管理297第11章 项目成本管理334第12章 项目质量管理358第13章…

KDE Plasma 6 将不支持较旧的桌面小部件

KDE Plasma 6 进行了一些修改&#xff0c;需要小部件作者进行调整。开发人员&#xff0c;移植时间到了&#xff01; KDE Plasma 6 是备受期待的桌面环境版本升级版本。 最近&#xff0c;其发布时间表公布&#xff0c;第一个 Alpha 版本将于 2023 年 11 月 8 日上线&#xff0…

基于蜻蜓算法优化概率神经网络PNN的分类预测 - 附代码

基于蜻蜓算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于蜻蜓算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于蜻蜓优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

云原生 黑马Kubernetes教程(K8S教程)笔记——第一章 kubernetes介绍——Master集群控制节点、Node工作负载节点、Pod控制单元

参考文章&#xff1a;kubernetes介绍 文章目录 第一章 kubernetes介绍1.1 应用部署方式演变传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上虚拟化部署&#xff1a;可以在一台物理机上运行多个虚拟机&#xff0c;每个虚拟机都是独立的一个环境&…

【操作系统】考研真题攻克与重点知识点剖析 - 第 3 篇:内存管理

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

【论文】利用移动性的比例公平蜂窝调度测量和算法

&#xff08;一支笔一包烟&#xff0c;一节论文看一天 &#xff09;&#xff08;一张纸一瓶酒&#xff0c;一道公式推一宿&#xff09; 摘要1. 引言2. 相关工作3. 模型和问题公式4. 预测FPF调度 &#xff08; P F &#xff09; 2 S &#xff08;PF&#xff09;^2S &#xff08;…

【MySQL系列】 第二章 · SQL(中)

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

PCA(主成分分析)数据降维技术代码详解

引言 随着大数据时代的到来&#xff0c;我们经常会面临处理高维数据的问题。高维数据不仅增加了计算复杂度&#xff0c;还可能引发“维度灾难”。为了解决这一问题&#xff0c;我们需要对数据进行降维处理&#xff0c;即在不损失太多信息的前提下&#xff0c;将数据从高维空间…

pyTorch Hub 系列#2:VGG 和 ResNet

一、说明 在上一篇教程中,我们了解了 Torch Hub 背后的本质及其概念。然后,我们使用 Torch Hub 的复杂性发布了我们的模型,并通过相同的方式访问它。但是,当我们的工作要求我们利用 Torch Hub 上提供的众多全能模型之一时,会发生什么? 在本教程中,我们将学习如何利用称为…

C语言——打印1000年到2000年之间的闰年

闰年&#xff1a; 1、能被4整除不能被100整除 2、能被400整除 #define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int year;for(year 1000; year < 2000; year){if((year%4 0) && (year%100!0) || (year%400 0)){printf("%d ",ye…