一、生产者消费者问题

1. 问题

  1. 假设仓库中只能放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费
  2. 如果仓库中没有产品,则生产者将产品放入仓库。否则停止生产并等待,直到仓库中的产品被消费者取走为止
  3. 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

2. 分析

生产者和消费者共享同一个资源,并且两者之间相互依赖,互为条件

  • 对于生产者,没有生产产品之前要通知消费者等待。而生产了产品之后又需要立即通知消费者
  • 对于消费者,在消费之后要通知生产者已经结束消费,需要生产新的产品
  • 在生产者消费者问题中,仅有synchronized是不够的
    • synchronized可阻止并发更新同一个共享资源,实现了同步
    • synchronized不能用来实现不同线程之间的通信

二、线程通信的方法

Java 中提供了几个方法解决线程之间的通信问题

方法名 作用
wait() 表示线程一致等待,直到其他线程通知。与sleep()不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

注:以上均是 Object 类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出IllegalMonitorStateExeception异常

三、解决方式

1. 管程法

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

例:
public class TestPC {public static void main(String[] args) {SyncContainer container = new SyncContainer();new Producer(container).start();new Consumer(container).start();}
}class Producer extends Thread {SyncContainer container;public Producer(SyncContainer container) {this.container = container;}@Overridepublic void run() {// 生产for (int i = 0; i < 100; i++) {container.push(new Chicken(i));System.out.println("生产了" + i + "只鸡");}}
}class Consumer extends Thread {SyncContainer container;public Consumer(SyncContainer container) {this.container = container;}@Overridepublic void run() {// 消费for (int i = 0; i < 100; i++) {System.out.println("消费了第" + container.pop().id + "只鸡");}}
}class Chicken {int id;public Chicken(int id) {this.id = id;}
}class SyncContainer {// 需要一个容器大小Chicken[] chickens = new Chicken[10];// 容器计数器int count = 0;// 生产者放入产品public synchronized void push(Chicken c) {// 如果容器满了,等待消费者消费if (count == chickens.length) {// 通知消费者,生产者等待try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 如果未满,需要丢入产品chickens[count] = c;count++;// 通知消费者可以消费了this.notifyAll();}// 消费者消费产品public synchronized Chicken pop() {// 判断能否消费if (count == 0) {// 等待生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 如果可以消费count--;Chicken chicken = chickens[count];// 通知生产者生产this.notifyAll();return chicken;}
}

2. 信号灯法

借助标志位判断,若为真消费者等待,若为假生产者等待

例:
public class TestPC2 {public static void main(String[] args) {TV tv = new TV();new Player(tv).start();new Watcher(tv).start();}
}// 生产者——演员
class Player extends Thread {TV tv;public Player(TV tv) {this.tv = tv;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {if (i % 2 == 0) {this.tv.play("快乐大本营");} else {this.tv.play("广告");}}}
}// 消费者——观众
class Watcher extends Thread {TV tv;public Watcher(TV tv) {this.tv = tv;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {tv.watch();}}
}// 产品——节目
class TV {// 演员录制时,观众等待 T// 观众观看时,演员等待 F// 录制的节目String show;// 标志位boolean flag = true;// 演员录制public synchronized void play(String show) {if (!flag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("演员录制了" + show);// 通知观众观看this.notifyAll();this.show = show;this.flag = !this.flag;}// 观众观看public synchronized void watch() {if (flag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("观众观看了" + show);// 通知演员录制this.notifyAll();this.flag = !this.flag;}
}

四、小结

  • 基于“锁”的同步方式需要线程不断地尝试去获得锁,失败了也需要继续尝试,这会很浪费资源,而等待/通知机制则能解决这一问题
  • 等待/通知机制使用的是同一个对象锁,如果两个线程使用的是不同对象锁,则不能用该机制通信

五、扩展

信号量

  • 关键字volatile,能够保证内存的可见性
  • 使用volatile修饰的变量,如果在一个线程里值发生了改变,那么在其它线程中都是立即可见的
  • 使用volatile修饰的变量需要进行原子操作

十、生产者消费者问题相关推荐

  1. java 生产者消费者模式_聊聊并发(十)生产者消费者模式

    本文首发于InfoQ   作者:方腾飞  校对:张龙 在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题.该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度. 为什么要使 ...

  2. Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型...

    一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...

  3. 秒杀多线程第十篇 生产者消费者问题

    继经典线程同步问题之后,我们来看看生产者消费者问题及读者写者问题.生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消 ...

  4. Java多线程(十):BlockingQueue实现生产者消费者模型

    BlockingQueue BlockingQueue.解决了多线程中,如何高效安全"传输"数据的问题.程序员无需关心什么时候阻塞线程,什么时候唤醒线程,该唤醒哪个线程. 方法介绍 ...

  5. Java(二十二) -- 生产者消费者模式

    目录 生产者消费者模式 汉堡类 容器类 生产者 消费者 测试类 案例:多线程并发卖票 生产者消费者模式 在一个生产环境中,生产者和消费者在同一时间段内共享同一块缓冲区,生产者负责向缓冲区添加数据,消费 ...

  6. 【Linux篇】第十六篇——生产者消费者模型

    生产者消费者模型 生产者消费者模型的概念 生产者消费者模型的特点 生产者消费者模型优点 基于BlockingQueue的生产消费者模型 基于阻塞队列的生产者消费者模型 模拟实现基于阻塞队列的生产消费模 ...

  7. Redis 学习笔记十 发布者订阅者模式与生产者消费者模式

    消息队列有两种场景 生产者消费者:一个消息只能有一个消费者 发布者订阅者:一个消息可以被多个消费者收到 redis从2.0版本开始支持pub/sub. 而Producer/Consumer是借助于re ...

  8. JAVA入门基础进阶(十四)—— 实现多线程、线程同步、生产者消费者

    文章目录 1.实现多线程 1.1简单了解多线程[理解] 1.2并发和并行[理解] 1.3进程和线程[理解] 1.4实现多线程方式一:继承Thread类[应用] 1.5实现多线程方式二:实现Runnab ...

  9. (二十二)操作系统-生产者·消费者问题

    文章目录 一.问题描述 二.问题分析 三.PV操作题目分析步骤 1. 关系分析 2. 整理思路 3. 设置信号量 4. 编写代码 四.能否改变相邻P.V操作的顺序? 五.小结 1. PV操作题目的解题 ...

最新文章

  1. Linux下Tomcat的安装配置
  2. 绝地求生5月22日服务器维护,绝地求生5月22日更新了什么内容 吃鸡5月22日维护公告...
  3. python的可变参数 *args 和关键字参数**kw
  4. evoc服务器长鸣报警显示正常,研祥工业服务器出大事了!
  5. php资源文件html,nginx 同一域名下分目录配置显示php,html,资源文件
  6. 怎么用便签在手机上记事?
  7. 物理学 物体的运动力学分析之牛顿三定律 单摆的MATLAB运动仿真(一)
  8. 概率论与数理统计 答案
  9. sklearn.metrics confusion_matrix注意事项
  10. 如何将域名指向本地服务器
  11. ENSP实验八——单区域OSPF的基本配置
  12. Android之微信界面设计
  13. java基本微信小程序的在线拼车系统 uniapp小程序
  14. 如何在线设计签名?教你签名设计办法
  15. 栈展开(stack unwinding)在destructors中的exceptions
  16. android studio运行时报错AVD Nexus_5X_API_P is already running解决办法
  17. 给通达信独立下单软件(tc.exe)加上快捷键 TCOEM.XML
  18. 面向未来,我们来聊一聊什么是现代化数据架构
  19. 手把手教你开发第一个HarmonyOS (鸿蒙)移动应用
  20. python使用 tkinter + you-get 实现视频下载器以及 pyinstaller 打包时的问题

热门文章

  1. 美国计算机视觉专业排名,你了解美国计算机视觉专业吗
  2. 华为智能家居app未能连接上远程云服务_智能家居平台介绍:华为HiLink
  3. 《我不是药神》——生如夏花
  4. 作为一名大数据工程师你需要掌握Spark深度学习
  5. 于明:APU能否接力取代迟暮的X86?
  6. C语言真的太强大了,C几乎无处不在!
  7. 关于队里面最菜的在博客打卡第六天这件事
  8. 面向对象:余愿,知你冷暖,懂你悲欢,与你共黄昏,也能问你粥可温
  9. 复合赋值运算符“+=、-=、*=、/=、%=”详解
  10. java实现上传zip/rar压缩文件,自动解压