传统的生产者消费者问题,防止虚假唤醒

简介: 传统的生产者消费者问题,防止虚假唤醒

synchronized锁和lock锁的区别

  1. synchronized是Java内置的关键字,lock是一个Java类(JUC下的接口 )
  2. synchronized是无法判断获取锁的状态,lock可以判断获取锁的状态
  3. synchronized是全自动的,会自动释放锁,lock是手动的,必须手动释放锁(unLock),如果不释放锁,会造成死锁
  4. synchronized比如有两个线程,线程1获得锁,阻塞,线程2会一直等,lock锁就不一定会一直等待, 可以用tryLock()方法尝试获取锁  
  1. synchronized默认是可重入锁,不可以中断的,非公平,由于它是Java关键字,不能进行修改,lock锁也是可重入锁,可以判断锁是否中断,可以自己设置公平锁或者不公平锁(参数为true即可变为公平锁,默认不传参且为不公平锁),使用起来比synchronized更加灵活方便
  2. synchronized适合少量的代码同步问题.lock锁适合大量的同步代码
  1. 传统的生产者消费者问题,防止虚假唤醒

线程之间的通信问题 生产者和消费者问题

生产者和消费者代码编写思路:

判断是否等待 进行业务处理 通知其他线程

    判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程

    传统(synchronized)线程通信代码简单实现,线程交替执行 A B同时操作同一个变量


package com.wyh.pc;
/**
 * @program: JUC
 * @description: 线程通信
 * @author: 魏一鹤
 * @createDate: 2022-02-12 21:12
 **/
/**
  *线程之间通信问题:生产者和消费者  等待唤醒,通知唤醒
 * 线程交替执行 A B同时操作同一个变量
**/
//生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程
//判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程
public class A {
public static void main(String[] args){
//线程操作资源类
        //创建资源类
        Data data=new Data();
//多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称
        new Thread(()->{
//从10循环+1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程A").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程B").start();
    }
}
//静态资源  独立耦合的 必须降低耦合性
class Data{
// 属性 数字变量
    private  int num=0;
//方法 数字+1 自增1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void increment() throws InterruptedException {
if(num!=0){
//值为0的时候等待,值为1的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num++;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我+1完毕了
        this.notifyAll();
    }
//方法 数字-1 自减1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void decrement() throws InterruptedException {
if(num==0){
//值为1的时候等待,值为0的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num--;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我-1完毕了
        this.notifyAll();
    }
}


通过打印发现,实现了简单的线程通信交互执行


image.png


不过,以上代码是有问题的,现在A,B两个线程可以正常执行,那么如果有更多线程呢?

现在再加上两个线程执行,也就是两个线程加两个线减,执行代码还会和最开始两个线程执行的结果一样吗?


package com.wyh.pc;
/**
 * @program: JUC
 * @description: 线程通信
 * @author: 魏一鹤
 * @createDate: 2022-02-12 21:12
 **/
/**
  *线程之间通信问题:生产者和消费者  等待唤醒,通知唤醒
 * 线程交替执行 A B同时操作同一个变量
**/
//生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程
//判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程
public class A {
public static void main(String[] args){
//线程操作资源类
        //创建资源类
        Data data=new Data();
//多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称
        new Thread(()->{
//从10循环+1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程A").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程B").start();
 //新加入两个线程
 new Thread(()->{
            //从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程C").start();
        new Thread(()->{
            //从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程D").start();
    }
}
//静态资源  独立耦合的 必须降低耦合性
class Data{
// 属性 数字变量
    private  int num=0;
//方法 数字+1 自增1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void increment() throws InterruptedException {
if(num!=0){
//值为0的时候等待,值为1的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num++;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我+1完毕了
        this.notifyAll();
    }
//方法 数字-1 自减1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void decrement() throws InterruptedException {
if(num==0){
//值为1的时候等待,值为0的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num--;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我-1完毕了
        this.notifyAll();
    }
}


这个问题也叫虚假唤醒因为在多线程中if只会判断一次,一般判断等待应该使用while循环判断,

如何解决虚假唤醒呢? 把if判断改为while循环判断即可 修改后代码如下



package com.wyh.pc;
/**
 * @program: JUC
 * @description: 线程通信
 * @author: 魏一鹤
 * @createDate: 2022-02-12 21:12
 **/
/**
  *线程之间通信问题:生产者和消费者  等待唤醒,通知唤醒
 * 线程交替执行 A B同时操作同一个变量
**/
//生产者和消费者代码编写思路:判断是否等待 进行业务处理 通知其他线程
//判断是否需要等待,判断完之后就干活,如果需要等待就等待,干完活就通知其他线程
public class A {
public static void main(String[] args){
//线程操作资源类
        //创建资源类
        Data data=new Data();
//多线程处理 把资源抛给线程去执行 这里采用lambda表达式简化代码 ,第二个参数是现成名称
        new Thread(()->{
//从10循环+1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程A").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程B").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程C").start();
new Thread(()->{
//从10循环-1
            //由于方法里面wait()方法需要抛异常,这里调用也需要抛异常
            for (int i = 0; i < 10; i++) {
try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"线程D").start();
    }
}
//静态资源  独立耦合的 必须降低耦合性
class Data{
// 属性 数字变量
    private  int num=0;
//方法 数字+1 自增1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void increment() throws InterruptedException {
//为了防止虚假唤醒,应该用while进行判断等待
        while (num!=0){
//值为0的时候等待,值为1的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num++;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我+1完毕了
        this.notifyAll();
    }
//方法 数字-1 自减1
    //用synchronized修饰方法保证多线程操作同步
    public  synchronized  void decrement() throws InterruptedException {
        //为了防止虚假唤醒,应该用while进行判断等待
        while(num==0){
//值为1的时候等待,值为0的时候操作
            //等待操作 wait需要抛出异常
            this.wait();
        }
num--;
        System.out.println(Thread.currentThread().getName() + "-->"+num);
//操作完之后通知其他线程,我-1完毕了
        this.notifyAll();
    }
}


再次运行发现结果是正常的


image.png


线程A-->1

线程B-->0

线程A-->1

线程B-->0

线程A-->1

线程B-->0

线程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

线程B-->0

线程C-->1

线程D-->0

线程C-->1

线程D-->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

线程C-->1

线程D-->0

线程C-->1

线程D-->0


目录
相关文章
|
SQL 索引
简单了解RBO、CBO和HBO
简单了解RBO、CBO和HBO
|
资源调度
JUC并发编程之同步器(Semaphore、CountDownLatch、CyclicBarrier、Exchanger、CompletableFuture)附带相关面试题
1.Semaphore(资源调度) 2.CountDownLatch(子线程优先) 3.CyclicBarrier(栅栏) 4.Exchanger(公共交换区) 5.CompletableFuture(异步编程)
338 0
|
6月前
|
搜索推荐 API 开发者
京东商品列表 API 接口全解析:从入门到精通
京东商品列表API是京东开放平台为开发者提供的核心数据接口,支持批量获取商品基础信息、价格、库存状态等多维度数据。它具备数据丰富性、灵活筛选与分页查询、稳定高效等特点,可满足市场分析、选品优化、比价工具及推荐系统开发等需求,为电商业务创新提供坚实支撑。通过标准化通道,助力第三方高效、合法地利用京东海量商品数据。
|
7月前
|
存储 监控 固态存储
RAID10怎么创建?RAID10创建详细步骤
RAID 10(RAID 1+0)是一种结合了RAID 1镜像与RAID 0条带化技术的存储阵列,兼具高性能和数据冗余优势。其创建需7步:硬件准备、配置RAID设备、选择级别、添加硬盘、设置条带化大小、保存退出及初始化阵列。RAID 10通过两两分组构建RAID 1镜像,再以RAID 0连接提升性能。但成本较高,且若单镜像内两盘同时损坏,数据将无法恢复。未来,SSD普及、NVMe应用及软件定义存储等趋势将进一步优化RAID 10性能。创建时需注意备份数据、选用可靠硬件并定期维护监控。
465 7
|
10月前
|
数据采集 数据挖掘 数据安全/隐私保护
4步教你用rvest抓取网页并保存为CSV文件
本文介绍如何使用R语言的`rvest`包抓取网页数据并保存为CSV文件,以界面新闻网站为例。通过设置代理IP(如亿牛云)、User-Agent和Cookie,增强访问稳定性和安全性。代码涵盖环境配置、数据抓取、解析及保存步骤,确保高效、稳定地获取网页数据。适用于数据分析和统计分析场景。
213 8
4步教你用rvest抓取网页并保存为CSV文件
|
7月前
|
缓存 运维 Java
Java静态代码块深度剖析:机制、特性与最佳实践
在Java中,静态代码块(或称静态初始化块)是指类中定义的一个或多个`static { ... }`结构。其主要功能在于初始化类级别的数据,例如静态变量的初始化或执行仅需运行一次的初始化逻辑。
236 4
|
前端开发 JavaScript 开发者
CSS进阶 - CSS Modules与预处理器简介
【6月更文挑战第17天】前端开发中,CSS Modules和预处理器(如Sass、Less)解决了大规模项目中CSS的管理难题,提升代码复用和维护性。CSS Modules提供局部作用域的类名,避免全局冲突,而预处理器扩展CSS功能,使代码更像编程语言。常见问题包括命名冲突和过度嵌套,可通过自动哈希、少嵌套、合理变量规划来解决。结合两者使用,遵循模块化和适度预处理原则,集成到构建工具中,能优化开发流程。这些技术是现代前端不可或缺的工具。
162 2
|
Oracle Java 关系型数据库
Mac电脑上安装和配置Flutter开发环境
Mac电脑上安装和配置Flutter开发环境
410 60
|
分布式计算 算法 关系型数据库
实时数仓 Hologres产品使用合集之如何优化查询性能
实时数仓Hologres的基本概念和特点:1.一站式实时数仓引擎:Hologres集成了数据仓库、在线分析处理(OLAP)和在线服务(Serving)能力于一体,适合实时数据分析和决策支持场景。2.兼容PostgreSQL协议:Hologres支持标准SQL(兼容PostgreSQL协议和语法),使得迁移和集成变得简单。3.海量数据处理能力:能够处理PB级数据的多维分析和即席查询,支持高并发低延迟查询。4.实时性:支持数据的实时写入、实时更新和实时分析,满足对数据新鲜度要求高的业务场景。5.与大数据生态集成:与MaxCompute、Flink、DataWorks等阿里云产品深度融合,提供离在线
Inno Setup磁盘跨越必须启用,因为程序大于21000000000
Inno Setup磁盘跨越必须启用,因为程序大于21000000000