生产者与消费者问题?

2023-11-10

生产者消费者模式是并发、多线程编程中经典的设计模式。

        简单来看,就是一个类负责生产,一个类负责消费。举例来说,一个变量,生产者不断增加这个变量,消费者不断减少这个变量。在互联网应用中,抢票机制就是应用了该模式,比如大麦网演唱会门票抢票,12306火车票抢票等。

1、生产者与消费者问题 synchronized 版本

 假设有两个线程A和B,操作同一个变量,A线程+1,B线程-1, 交替循环进行

package cn.dczh.juc;

/**
 * @ClassName: A
 * @description: A线程 B线程 操作同一个变量 num= 0  A:+1   B:-1
 * @author: 
 * @date: 2022/3/16 21:34
 * @version: 1.0
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();

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

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

class Data {

    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        if (num != 0) {
            //等待
            this.wait();
        }
        //加一
        num++;
        System.out.println(Thread.currentThread().getName() + "线程加一:" + num);
        //通知其他线程 我加一完毕了
        this.notifyAll();
    }

    public synchronized void decr() throws InterruptedException {
        if (num == 0) {
            //等待
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "线程减一:" + num);
        //通知其他线程 我减一完毕了
        this.notifyAll();
    }
}

2、如果有四个线程A、B、C、D同时执行呢?(虚假唤醒问题?)

package cn.dczh.juc;

/**
 * @ClassName: A
 * @description: A线程 B线程 操作同一个变量 num= 0  A:+1   B:-1
 * @author: 
 * @date: 2022/3/16 21:34
 * @version: 1.0
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();

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

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

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

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

class Data {

    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        if (num != 0) {
            //等待
            this.wait();
        }
        //加一
        num++;
        System.out.println(Thread.currentThread().getName() + "线程加一:" + num);
        //通知其他线程 我加一完毕了
        this.notifyAll();
    }

    public synchronized void decr() throws InterruptedException {
        if (num == 0) {
            //等待
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "线程减一:" + num);
        //通知其他线程 我减一完毕了
        this.notifyAll();
    }
}

该代码执行结果为:

这种现象称为虚假唤醒 ! 如何解决这种问题呢?  

将代码中的if判断改文文档中的while判断即可

class Data {

    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        while (num != 0) {
            //等待
            this.wait();
        }
        //加一
        num++;
        System.out.println(Thread.currentThread().getName() + "线程加一:" + num);
        //通知其他线程 我加一完毕了
        this.notifyAll();
    }

    public synchronized void decr() throws InterruptedException {
        while (num == 0) {
            //等待
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "线程减一:" + num);
        //通知其他线程 我减一完毕了
        this.notifyAll();
    }
}

执行结果为:

 3、生产者与消费者问题 JUC版本

 

package cn.dczh.juc;

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

/**
 * @ClassName: B
 * @description:
 * @author: 
 * @date: 2022/3/16 22:08
 * @version: 1.0
 */
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data.incr();
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data.decr();
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data.incr();
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data.decr();
        }, "D").start();

    }
}

class Data2 {

    private int num = 0;
    //获取lock锁
    final Lock lock = new ReentrantLock();
    //通过lock锁获取对象监视器
    final Condition condition = lock.newCondition();

    public void incr() {
        lock.lock();
        try {
            while (num != 0) {
                //等待
                condition.await();
            }
            //加一
            num++;
            System.out.println(Thread.currentThread().getName() + "线程加一:" + num);
            //通知其他线程 我加一完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void decr() {
        lock.lock();
        try {
            while (num == 0) {
                //等待
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "线程减一:" + num);
            //通知其他线程 我减一完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}


4、Condition实现精种通知、唤醒线程 

 现有四个方法printA、printB、printC、printD,由四个线程A、B、C、D执行,A线程执行printA方法,执行完毕后唤醒B线程,执行printB方法,执行完毕后唤醒C线程执行printC方法,执行完毕后唤醒D线程,执行pringD方法,执行完毕后唤醒A线程 依此循环执行;

package cn.dczh.juc;

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

/**
 * @ClassName: C
 * @description:
 * @author: 
 * @date: 2022/3/16 22:22
 * @version: 1.0
 */
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(() -> { for (int i = 0; i < 10; i++) data.printA(); }, "A").start();
        new Thread(() -> { for (int i = 0; i < 10; i++) data.printB(); }, "B").start();
        new Thread(() -> { for (int i = 0; i < 10; i++) data.printC(); }, "C").start();
        new Thread(() -> { for (int i = 0; i < 10; i++) data.printD(); }, "D").start();

    }
}

class Data3 {

    //获取lock锁
    final Lock lock = new ReentrantLock();

    final Condition conditionA = lock.newCondition();
    final Condition conditionB = lock.newCondition();
    final Condition conditionC = lock.newCondition();
    final Condition conditionD = lock.newCondition();

    //1:A线程 2:B线程 3:C线程 4:D线程
    private int flag = 1;

    public void printA(){
        try {
            lock.lock();
            while (flag != 1){
                //等待
                conditionA.await();
            }
            System.out.println("线程"+Thread.currentThread().getName()+"执行完毕!");
            //修改标示为2
            flag = 2;
            //唤醒指定的线程 B
            conditionB.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printB(){
        try {
            lock.lock();
            while (flag != 2){
                //等待
                conditionB.await();
            }
            System.out.println("线程"+Thread.currentThread().getName()+"执行完毕!");
            //修改标示为2
            flag = 3;
            //唤醒指定的线程 C
            conditionC.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printC(){
        try {
            lock.lock();
            while (flag != 3){
                //等待
                conditionC.await();
            }
            System.out.println("线程"+Thread.currentThread().getName()+"执行完毕!");
            //修改标示为2
            flag = 4;
            //唤醒指定的线程 D
            conditionD.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printD(){
        try {
            lock.lock();
            while (flag != 4){
                //等待
                conditionD.await();
            }
            System.out.println("线程"+Thread.currentThread().getName()+"执行完毕!");
            //修改标示为2
            flag = 1;
            //唤醒指定的线程 A
            conditionA.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


}

执行结果如下:

小结 

 锁:锁的是new this 锁的是对象,static Clazz 锁的是唯一的类模板;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

生产者与消费者问题? 的相关文章

  • 一次线上的GC问题排查

    6 19号下午 线上系统出现了一次实时链路数据 不通畅的问题 业务方反应更新的增量数据没有流入到HA3搜索集群 登录机器后检查日志后发现 在周六晚上到周天下午 cr search merge 机器人schema统一 表增量数据猛增 初步估计
  • 多线程-Thread类的常用方法及使用场景

    众所周知 操作线程就必须熟读线程的API方法 万一你开个多线程刹不住车就歇菜了 下面就介绍一些API基本用法 包括sleep join yield interrupt sleep 让当前线程睡一会 原生用法Thread sleep 毫秒 会
  • 安全线程的集合

    1 CopyOnWriteArrayList package com kuang unsafe import java util import java util concurrent CopyOnWriteArrayList java u
  • 面试官:说说CountDownLatch,CyclicBarrier,Semaphore的原理?

    CountDownLatch CountDownLatch适用于在多线程的场景需要等待所有子线程全部执行完毕之后再做操作的场景 举个例子 早上部门开会 有人在上厕所 这时候需要等待所有人从厕所回来之后才能开始会议 public class
  • <并发编程>学习笔记------(一) 并发相关理论

    前面 并发编程可以总结为三个核心问题 分工指的是如何高效地拆解任务并分配给线程 同步指的是线程之间如何协作 互斥则是保证同一时刻只允许一个线程访问共享资源 并发相关理论 可见性 原子性和有序性 核心矛盾 CPU 内存 I O 设备的速度差异
  • Java JUC概述

    Java JUC Java Util Concurrent 是 Java 平台提供的并发编程工具包 它提供了一系列的工具类和接口 用于简化多线程编程 JUC 中的类和接口都是基于 Java 平台的底层并发原语 如锁 信号量 原子变量等 实现
  • 并发编程系列之volatile内存语义

    前言 前面介绍顺序一致性模型时 我们提到了程序如果正确的同步就会具备顺序一致性 这里所说的同步泛指广义上的同步 其中包括就包括同步原语volatile 那么volatile声明的变量为什么就能保证同步呢 这又是如何实现的呢 今天就让我们一起
  • synchronized与(ReentrantLock)Lock的对比区别

    类别 synchronized Lock 存在层次 Java关键字 属于原生语法层面 需要jvm实现 而Lock它是JDK 1 5之后提供的API层面的互斥锁 需要lock 和unlock 方法配合try finally语句块来完成 锁的释
  • JUC并发编程之ReentrantLock

    1 非公平锁实现原理 加锁解锁流程 构造器默认实现的是非公平锁 public ReentrantLock sync new NonfairSync NonfairSync 继承 Sync Sync 继承 AbstractQueuedSync
  • AQS原理解析及源码分析

    目录 1 介绍下AQS几个重要的组件 2 内部成员变量state 3 同步队列NODE 4 等待队列 condition AbstractQueuedSynchronizer又称为队列同步器 后面简称AQS AQS的核心思想是 如果被请求的
  • ReentrantLock实现PV操作-模拟多线程竞争数据库连接池资源场景

    使用ReentrantLock Condition模拟PV操作 实现多线程竞争数据库连接池资源 资源耗尽后阻塞等待 归还资源后唤醒阻塞线程的场景 代码中为10个线程竞争5个数据库连接资源 ConnectionPool class 连接池 C
  • shell编程笔记3--shell并发

    shell编程笔记3 shell并发 shell编程笔记3 shell并发 介绍 并发方法 1 简单后台方式 2 普通控制并发量方式 3 通过管道控制并发量 参考文献 shell编程笔记3 shell并发 介绍 在shell中适当使用并发功
  • 从0实现基于Linux socket聊天室-实现聊天室的公聊、私聊功能-4

    前面文章链接如下 从0实现基于Linux socket聊天室 多线程服务器模型 1 从0实现基于Linux socket聊天室 多线程服务器一个很隐晦的错误 2 从0实现基于Linux socket聊天室 实现聊天室的登录 注册功能 3 上
  • wait notify正确使用方式

    wait notify正确使用方式 假设 当线程 Thread03 在1 100中找出77并输出后 Thread01 输出所有1 100中的奇数 当线程 Thread03 在1 100中找出88并输出后 Thread02 输出所有1 100
  • Lock锁

    Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作 它们允许更灵活的结构化 可能具有完全不同的属性 并且可以支持多个相关联的对象Condition 1 传统的synchronized package cn d
  • 锁介绍名词解释&&Lock && synchronized

    各种锁名词解释及应用 一 名词解释 1 乐观锁 VS 悲观锁 2 自旋锁 VS 适应性自旋锁 3 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁 4 公平锁 VS 非公平锁 5 可重入锁 VS 非可重入锁 6 独享锁 VS 共享锁 二
  • 死锁产生条件和解决办法

    死锁 死锁产生的四个条件 产生死锁必须同时满足以下四个条件 只要其中任一条件不成立 死锁就不会发生 互斥条件 线程要求对所分配的资源 如打印机 进行排他性控制 即在一段时间内某资源仅为一个线程所占有 此时若有其他线程请求该资源 则请求线程只
  • JUC并发编程共享模型之管程(三)(中)

    4 5Monitor概念 Java 对象头 以 32 位虚拟机为例 在32位虚拟机中 1个机器码等于4字节 也就是32bit 在64位虚拟机中 1个机器码是8个字节 也就是64bit 普通对象 数组对象 其中Mark Word 结构为 最后
  • Java线程(Thread)生命周期的6种状态

    当线程被创建并启动以后 它既不是一启动就进入了执行状态 也不是一直处于执行状态 在线程的生命周期中 可能处于不同的状态 java lang Thread State 列举出了这6种线程状态 线程状态 导致状态发生条件 New 新建 线程刚被
  • 万文详解JUC(超详细)

    生命无罪 健康万岁 我是laity 我曾七次鄙视自己的灵魂 第一次 当它本可进取时 却故作谦卑 第二次 当它在空虚时 用爱欲来填充 第三次 在困难和容易之间 它选择了容易 第四次 它犯了错 却借由别人也会犯错来宽慰自己 第五次 它自由软弱

随机推荐

  • QT的qRegisterMetaType和qRegisterMetaType

    以下只为自己的问题做个记录 参考理解 qRegisterMetaType使用方法如下 1 注册位置 在第一次使用此类链接跨线程的signal slot之前 一般在当前类的构造函数中进行注册 2 注册方法 在当前类的顶部包含 include
  • 华为OD机试 - 称砝码(Java)

    题目描述 现有n种砝码 重量互不相等 分别为 m1 m2 m3 mn 每种砝码对应的数量为 x1 x2 x3 xn 现在要用这些砝码去称物体的重量 放在同一侧 问能称出多少种不同的重量 输入描述 对于每组测试数据 第一行 n 砝码的种数 范
  • 小程序跳转带参数

    携带参数 pages reg reg wx navigateTo url pages promise promise name pages reg reg 接收参数 onLoad function arg console log sssss
  • Python安装教程步骤2:Windows中创建虚拟环境安装Pytorch并在PyCharm中配置虚拟环境

    python安装教程步骤2 windows中Anaconda创建虚拟环境安装pytorch并在pycharm中使用虚拟环境 作者介绍 windows中Anaconda创建虚拟环境安装pytorch 1 添加镜像源 2 创建虚拟环境 3 进入
  • SVN 客户端 设置上传过滤文件类型和文件夹

    第一步 编辑SVN 全局配置 修改如下内容 global ignores o lo la al libs so so 0 9 a pyc pyo class target target classpath project settings
  • python-爬虫

    对于自动化办公而言 网络数据的批量获取完数据可以节约相当的时间 因此爬虫在自动化办公中占据了一个比较重要的位置 Requests简介 Requests是一款目前非常流行的http请求库 使用python编写 能非常方便的对网页Request
  • Winform 登录页面创建和设置

    Winform 登录页面设置 自带的Form1进行改造 更换名字为FrmLogin 简单先不放置容器 直接先托两个TextBox 再放两个lable分别改好名字 设置密码框的textbox的PasswordChar设置为 再拖两个普通的bu
  • 语音识别研究的四大前沿方向

    以下转自 http blog csdn net whaoxysh article details 19402337 鸡尾酒会问题 cocktail party problem 是在计算机语音识别领域的一个问题 当前语音识别技术已经可以以较高
  • 微信小程序实现滑动/点击切换Tab

    背景 swiper scroll view实现滑动 点击切换Tab 以及scroll left的使用 文末分享源代码 记得点赞 关注 收藏 1 实现效果 2 实现步骤 2 1 scroll view实现tab列表 scroll view 可
  • react+antd 修改主题色

    项目使用的时 react 框架 和 ant design ui组件库 antd 官网中对定制主体是这样说的 详见 https ant design docs react customize theme cn 官网说的是 antd 的样式使用
  • Python中的几大产生随机数的函数range,randint以及xrange函数

    文章目录 1 xrange 函数 2 range 函数 3 randint 函数 示例代码 1 xrange 函数 现在python的版本大多都已经采用python3 x版本 python2 x版本已经逐渐不被人们所使用 xrange 函数
  • linux图形界面切换到字符界面

    redhat linux图形界面切换到字符界面 1 X Window图形界面和字符界面自由切换 一 图形界面切换到字符界面 在X Window图形操作界面中按 Alt Ctrl Fn n 1 6 就可以进入Console字符操作界面 这就意
  • C++强制类型转换运算符-dynamic_cast、const_cast、static_cast、reinterpret_cast、dynamic_pointer_cast、const_pointer

    C 强制类型转换 dynamic cast 回答的问题 是否能将某个对象的地址安全地赋值给一个特定类型的指针 同时也回答了强制类型转换是否安全的问题 dynamic cast用于类继承层次间的指针或引用转换 主要还是用于执行安全的向下转型
  • AddBinary[LeetCode]

    掌握了刚才的方法 这类问题都解决了 class Solution public string addBinary string a string b int m a size 1 int n b size 1 int carry 0 str
  • kaggle数据分析实践项目练习——人力资源分析

    学习完 利用python进行数据分析 之后 学习了Numpy pandas matplotlib几个包的使用 于是着手准备在kaggle上找数据集进行练习 在kaggle中找到了人力资源分析项目 看到不少人拿这个项目练手 本文将会分析为何公
  • 【Hadoop系列】linux下 root用户免密码登录远程主机 ssh

    SSH原理 Hadoop系列 linux SSH原理解析 操作环境 CentOS 6 5 操作对象 用户A主机和远程主机B 正文部分 斜体加粗代表linux指令 linux下 非root用户免密码登录远程主机 ssh请转至此链接 XXXXX
  • Linux CentOS7命令及命令行

    Linux CentOS7中命令及命令行是非常重要的概念 对大多数初学者来说是既熟悉又了解甚少 本文初步讨论这方面的内容 与同行者交流 一 命令 命令又称为指令 英语命令 command 可用简写cmd表示 在终端命令行中执行的一段字符 一
  • QT5.14.2+VS2017环境安装

    1 下载Qt5 14 2以及qt vs插件 1 Qt官网下载地址 操作系统是win10企业版 在目录qt下找到程序qt opensource windows x86 5 14 2 exe 在目录vsaddin下找到程序qt vsaddin
  • 时间基础概念及Linux中的时间函数

    时间基础概念及Linux中的时间函数 时间相关概念 GMT 时间 UTC 时间 时区 Time Zone 夏令时 DST 本地时间 localtime Linux 系统中的时间 时钟基础概念 系统节拍数 jiffies Linux系统查看时
  • 生产者与消费者问题?

    生产者消费者模式是并发 多线程编程中经典的设计模式 简单来看 就是一个类负责生产 一个类负责消费 举例来说 一个变量 生产者不断增加这个变量 消费者不断减少这个变量 在互联网应用中 抢票机制就是应用了该模式 比如大麦网演唱会门票抢票 123