【多线程】线程间通信 之虚假唤醒和中断

news2024/12/27 11:56:41

两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量+1,一个线程对该变量-1,实现交替,来10轮,变量初始值为0,以实现此问题作为引入,简化我们的理解

文章目录

    • 一、两个线程synchronized写法-结果无问题
    • 二(一)、四个线程synchronized写法-问题及解决办法
    • 二(二)、4线程问题 解决办法1:使用while进行条件判断
    • 二(三)、4线程问题 解决办法2:使用Lock和Condition实现线程间通信

一、两个线程synchronized写法-结果无问题

package com.atguigu.signcenter.thread;

/**
 * 线程之间的通信-两个线程synchronized写法
 * @author: jd
 * @create: 2024-09-02
 */
public class ThreadWaitNotifyDemo {
    public static void main(String[] args) {
        AirConditioner airConditioner = new AirConditioner();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
    }
}

class AirConditioner {  // 资源类
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        // 1. 判断
        if (number != 0) {
            this.wait();
        }
        // 2. 干活
        number++;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3. 通知
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        // 1. 判断
        if (number == 0) {
            this.wait();
        }
        // 2. 干活
        number--;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3. 通知
        this.notifyAll();
    }
}

结果:

A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0
A	1
B	0

二(一)、四个线程synchronized写法-问题及解决办法

换成4个线程会导致错误,虚假唤醒。
原因:在java多线程判断时,不能用if,程序出事出在了判断上面,突然有一添加的线程进到if了,突然中断了交出控制权,没有进行验证,而是直接走下去了,加了两次,甚至多次。
在这里插入图片描述

中断和虚假唤醒是可能产生的,所以要用loop循环,if只判断一次,while是只要唤醒就要拉回来再判断一次。if换成while。(也就是说在四个线程下,有可能两个increment线程都在if中wait,当其被唤醒时,不会再次判断number是否满足条件,而直接执行number++,因此会导致number大于1的情况,同理也会出现number小于0的情况)

如果使用if判断,则会导致虚假唤醒:代码及现象

package com.atguigu.signcenter.thread;

/**
 * 线程之间的通信-两个、四个线程synchronized写法
 * @author: jd
 * @create: 2024-09-02
 */
public class ThreadWaitNotifyDemo {
    public static void main(String[] args) {
        AirConditioner airConditioner = new AirConditioner();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}

class AirConditioner {  // 资源类
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        // 1. 判断
        if (number != 0) {
            this.wait();
        }
        // 2. 干活
        number++;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3. 通知
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        // 1. 判断
        if (number == 0) {
            this.wait();
        }
        // 2. 干活
        number--;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3. 通知
        this.notifyAll();
    }
}

现象

A	1
B	0
A	1
B	0
C	1
B	0
A	1
B	0
C	1
D	0
B	-1
B	-2
B	-3
B	-4
B	-5
B	-6
A	-5
D	-6
D	-7
D	-8
D	-9
D	-10
D	-11
D	-12
D	-13
D	-14
C	-13
A	-12
C	-11
A	-10
C	-9
A	-8
C	-7
A	-6
C	-5
A	-4
C	-3
A	-2
C	-1

图示为什么会出现问题
在使用if判断两个线程的情况下,阻塞的线程只有两种情况,此时不会出现任何问题;
而使用if在四个线程的情况下,可能存在这种情况:

  1. 最开始+线程进行了增加操作NotifyAll;
  2. 此时+'线程抢占到执行权,进入if判断进入阻塞状态;
  3. +线程又抢到了执行权,同样进入if判断阻塞;
  4. -线程抢占执行权进行减操作,NotifyAll;
  5. +'线程抢占执行权,进行增加操作,NotifyAll;
  6. +线程抢占执行权,进行增加操作 (此时便出现了number=2的情况)
    使用while就不会出现这种问题,因为在NotifyAll线程激活运行后,会进行二次判断!
    在这里插入图片描述

二(二)、4线程问题 解决办法1:使用while进行条件判断

解决此问题:
使用while进行条件判断

  1. 高内聚第耦合的前提下,线程操作资源类
  2. 判断/干活/通知
  3. 多线程交互中,必须要防止多线程的虚假唤醒,也即(在多线程的判断中不许用if只能用while)
    解决代码:
package com.atguigu.signcenter.thread;

/**
 * 线程之间的通信-两个、四个线程synchronized写法
 * @author: jd
 * @create: 2024-09-02
 */
public class ThreadWaitNotifyDemo {
    public static void main(String[] args) {
        AirConditioner airConditioner = new AirConditioner();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}
/*
class AirConditioner {  // 资源类
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        // 1. 判断
        if (number != 0) {
            this.wait();
        }
        // 2. 干活
        number++;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3. 通知
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        // 1. 判断
        if (number == 0) {
            this.wait();
        }
        // 2. 干活
        number--;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3. 通知
        this.notifyAll();
    }*/

class AirConditioner {  // 资源类
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        // 1. 判断
        while (number != 0) {
            this.wait();
        }
        // 2. 干活
        number++;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3. 通知
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        // 1. 判断
        while (number == 0) {
            this.wait();
        }
        // 2. 干活
        number--;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        // 3. 通知
        this.notifyAll();
    }
}

正常结果:

A	1
B	0
A	1
B	0
A	1
B	0
C	1
B	0
A	1
B	0
C	1
B	0
A	1
D	0
C	1
B	0
A	1
D	0
C	1
B	0
A	1
D	0
C	1
B	0
A	1
D	0
C	1
B	0
A	1
D	0
C	1
D	0
A	1
D	0
C	1
D	0
C	1
D	0
C	1
D	0

二(三)、4线程问题 解决办法2:使用Lock和Condition实现线程间通信

通过Java8的Lock和Condition接口(await、signal、signalAll),可以替换synchronized与Object monitor方法(wait、notify、notifyAll)
在这里插入图片描述
这里我们还是使用3.2中的例子,4个线程,两个打印1两个打印0,让其交替打印,分别打印十次

package com.atguigu.signcenter.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: jd
 * @create: 2024-09-02
 */
public class ThreadWaitNotifyDemo2 {
    public static void main(String[] args) {
        AirConditioner2 airConditioner = new AirConditioner2();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    airConditioner.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}

class AirConditioner2 {  // 资源类
    private int number = 0;

    // 使用java8 lock 和 condition接口实现
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            // 1. 判断
            while (number != 0) {
                condition.await(); // this.wait();
            }
            // 2. 干活
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 3. 通知
            condition.signalAll(); // this.notifyAll();
        }catch (Exception e) {

        }finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            // 1. 判断
            while (number == 0) {
                condition.await(); // this.wait();
            }
            // 2. 干活
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 3. 通知
            condition.signalAll(); // this.notifyAll();
        }catch (Exception e) {

        }finally {
            lock.unlock();
        }
    }
}

码字不易,请大家多多指教~

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

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

相关文章

EasyExcel实现复杂Excel的导入

最近项目中遇到一个复杂的Excel的导入&#xff0c;并且数据量较大。因为数据不规则&#xff0c;所以只能使用POI进行自定义读取&#xff0c;但是发现数据量大之后&#xff0c;读取数据非常耗时。后面换成EasyExcel&#xff0c;性能起飞。 1. Excel样板 如上图&#xff0c;需要…

leetcode 12. 整数转罗马数字

解题思路 1.首先&#xff0c;将值与对应字符用字典来表示&#xff0c;然后将符号对应的值按有序顺序存储至列表中sums 2.将要转换的整数转换成列表形式&#xff0c;遍历列表&#xff0c;每次遍历&#xff1a; 将值取出来算出对应所在位置的实际值 如 32 对应列表 [3,2] 则 3实…

【二叉搜索树】K型与KV型二叉搜索树简单实现

关于我&#xff1a; 睡觉待开机&#xff1a;个人主页 个人专栏: 《优选算法》《C语言》《CPP》 生活的理想&#xff0c;就是为了理想的生活! 作者留言 PDF版免费提供&#xff1a;倘若有需要&#xff0c;想拿我写的博客进行学习和交流&#xff0c;可以私信我将免费提供PDF版。…

中国电子学会Python3级等级考试202403客观题解析1

一、单项选择题 1、在 Python 中&#xff0c;hex(2023)的功能是&#xff1f;&#xff08; &#xff09; A 将十进制数 2023 转化为十六进制数 B 将十进制数 2023 转化为八进制数 C 将十六进制数 2023 转化为十进制数 D 将八进制数 2023 转化为十进制数 答案&#xff1a;A…

linux如何查看内存条是ddr几代

在 Linux 系统中&#xff0c;可以通过以下几种方法查看内存条的类型和代数&#xff08;如 DDR3、DDR4 等&#xff09;&#xff1a; 1. 使用 dmidecode 命令 dmidecode 是一个工具&#xff0c;它可以从系统的 DMI 表&#xff08;也称为 SMBIOS 表&#xff09;中提取硬件信息&a…

半导体制造业“电”亮未来,APView500护航电能质量新篇章

在科技日新月异的今天&#xff0c;半导体制造业作为信息技术的心脏&#xff0c;其生产效率与稳定性直接关乎国家科技实力与产业升级。然而&#xff0c;这一高精尖领域却长期面临电能质量问题的严峻挑战&#xff0c;尤其是谐波污染与电压暂降/中断两大难题&#xff0c;如同潜伏的…

springboot中上传图片到阿里云的oss云存储

上篇演示了如何将图片上传到本地&#xff0c;但是在实际项目中&#xff0c;这样是很占服务器存储空间的。所以&#xff0c;我们一般的解决方案是使用oss云存储。这里就结合阿里云的oss来实现下这个业务功能。 安装依赖 参考官网即可,https://help.aliyun.com/zh/oss/developer…

SOEX从去中心化的链上社交关系到创收策略

是时候摆脱传统的在线社区&#xff0c;真正进入 Web3 了&#xff0c;利用区块链的力量&#xff0c;并理解社交互动的意义远不止分享内容或复制交易。代币化将赋能参与提升到一个全新的水平&#xff0c;并带来一系列新的机会。 社交网络可以发挥强大的作用&#xff0c;尤其是从…

Carmaker Hil部署

本文主要简单介绍carmaker HIl的部署&#xff08;Windows环境&#xff09; carmaker HIL的上位机与Xpack 4的下位机通过一个以太网口进行连接&#xff0c;然后打开上位机的网络连接配置&#xff0c;并关闭防火墙&#xff1a; 打开carmaker HIL的配置&#xff0c;对网络连接进…

这一届“出道”的数字人,已经拿捧上了“铁饭碗”

文 | 智能相对论 作者 | 陈泊丞 好消息&#xff01;你心心念念的事业单位发录取公告了&#xff01; 坏消息&#xff01;他们没录你&#xff0c;录了个数字人。 图片来源网络 随着数字人技术的突破&#xff0c;越来越多的传统企业和机构开始用上了“数字员工”。 甚至很多中…

RFID光触发标签在文件柜管理中的创新应用

在当今信息化时代&#xff0c;文件管理对于企业和机构的重要性不言而喻。传统的文件柜管理方式存在诸多问题&#xff0c;如查找文件困难、管理效率低下、安全性难以保障等。而 RFID 光触发标签技术的出现&#xff0c;为文件柜管理带来了全新的解决方案。 一、传统文件柜管理的…

【C++ Primer Plus习题】9.1

问题: 解答: main.cpp #include <iostream> #include <string> #include "golf.h" using namespace std;#define SIZE 5int main() {golf ann;setgolf(ann, "AnnBirdfree", 24);golf andy;setgolf(andy);showgolf(ann);showgolf(andy);return…

如何组织一场考试答题?

&#x1f469;&#xff1a;我想组织一场考试答题&#xff0c;考完后可以导出所有考生的成绩&#xff0c;我还需要查出哪些人是没有参加考试的&#xff0c;这个能实现吗&#xff1f; &#x1f64b;&#xff1a;支持的 下面将以【如何组织一场考试答题】为主流程展开介绍 &#x…

第L2周:机器学习-线性回归

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目标&#xff1a; 学习简单线性回归模型和多元线性回归模型通过代码实现&#xff1a;通过鸢尾花花瓣长度预测花瓣宽度 具体实现&#xff1a; &#xff08;一&…

走进酿酒车间:探寻白酒酿造的每一步

在古老的华夏大地上&#xff0c;有一种传统而不同的技艺——白酒酿造。它不仅承载了中华民族千年的文化积淀&#xff0c;更蕴含了无数酿酒师的匠心与智慧。今天&#xff0c;让我们一同走进豪迈白酒&#xff08;HOMANLISM&#xff09;的酿酒车间&#xff0c;探寻那神秘而又精彩的…

20240903 每日AI必读资讯

Claude 3.5 Sonnet对免费用户关闭&#xff1f; - 免费用户无法使用Claude 3.5 Sonnet模型&#xff0c;只能使用功能简单的Claude 3 Haiku模型。 - Claude 3.5 Sonnet在性能、理解能力、运行速度和视觉处理方面都有显著提升&#xff0c;成为行业新标杆。 - 关闭可能影响用户使…

解决jupyter notebook启动需要密码的问题

解决方法 在运行界面输入 jupyter notebook list 之后运行界面会输出token值&#xff0c;将对应地址后的token复制到密码栏中即可

Java题集(由入门到精通)01

此系列文章收录大量Java经典代码题&#xff08;也可以算是leetcode刷题指南&#xff09;&#xff0c;希望可以与大家一起努力学好Java。3、2、1&#xff0c;请看&#xff01; 目录 1.根据输入的运算符&#xff08;、-、*、/&#xff09;进行计算 2.随机生成一个整数&#xf…

AJAX基础与进阶

一、express基本使用 1. 在最外层启动终端&#xff0c;添加文件 2. 创建 express 框架 // 1. 引入express const express require(express);// 2. 创建应用对象 const app express();// 3. 创建路由规则 //request 是对请求报文的封装 //response 是对响应报文的封装 app.g…

021集—— 数据的大小端序转换——C#学习笔记

整形数据的大小端序转换&#xff1a; 代码如下&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApp1 {class a {public static int EndianReverse(int oldvalue) {//in…