基于线程实现的生产者消费者模型(Object.wait(),Object.notify()方法)
需求背景
利用线程来模拟生产者和消费者模型
系统建模
这个系统涉及到三个角色,生产者,消费者,任务队列,三个角色之间的关系非常简单,生产者和消费者拥有一个任务队列的引用,生产者负责往队列中放置对象(id),消费者负责从队列中获取对象(id),其关联关系如下

方案1
因为是多线程操作,所以对任务的存取都要使用线程同步加锁机制,看一下我们的TaskQueue类,两个主方法都加了synchronized修饰,这就意味着,一个时间点只可能有一个线程对这个方法进行操作
TaskQueue类代码
- package com.crazycoder2010.thread;
- public class TaskQueue {
- private int id;
- public synchronized void put(int id){
- this.id = id;
- System.out.println("Put:"+id);
- }
- public synchronized int get(){
- System.out.println("Got:"+this.id);
- return this.id;
- }
- }
再来看一下生产者,这个主要不停的往TaskQueue中添加新对象,这里我们搞了个死循环,运行时不断的对当前数字做加一操作如下
Producer类代码
- package com.crazycoder2010.thread;
- public class Producer implements Runnable{
- private TaskQueue taskQuery;
- public Producer(TaskQueue taskQuery){
- this.taskQuery = taskQuery;
- new Thread(this).start();
- }
- @Override
- public void run() {
- int i = 0;
- while(true){
- taskQuery.put(i++);
- }
- }
- }
消费者对象也是基于线程实现,在循环中不停的轮训TaskQuery.get()来获取当前对象
Consumer类
- package com.crazycoder2010.thread;
- public class Consumer implements Runnable {
- private TaskQueue taskQuery;
- public Consumer(TaskQueue taskQuery){
- this.taskQuery = taskQuery;
- new Thread(this).start();
- }
- @Override
- public void run() {
- while(true){
- taskQuery.get();
- }
- }
- }
运行一下,看看结果是否是我们所期望的那样
Launcher类代码
- package com.crazycoder2010.thread;
- public class Launcher {
- public static void main(String[] args) {
- TaskQueue taskQuery = new TaskQueue();
- new Producer(taskQuery);
- new Consumer(taskQuery);
- System.out.println("Press Control-C to stop.");
- }
- }
输出结果:
...
Put:58
Put:59
Put:60
Put:61
Put:62
Put:63
Put:64
Got:64
Got:64
Got:64
Put:65
Put:66
...
问题出现在哪里呢?
从结果输出我们可以看出,生产者的对象放入顺序是按次序进行的,但是消费者读取对象的数序很奇怪,一段时间内读取同一个数值,这个是怎么造成的呢?
我们知道,当启动线程后线程什么时候被jvm所调度是不确定的,上面的结果在不同的机器上运行很可能得到不同的结果,当Producer线程被调度运行一段时间后,线程Consumer获取到运行资格,然后从TaskQueue中取对象,这个时候由于Producer处于等待被调度状态,所以TaskQueue中的id一直都是个固定值,所以这个时候Consumer获取到的一直都是Producer在被jvm设置为等待状态那一刻的值,运行一段时间,Producer又被jvm调度器调度,获取运行资格,这个时候id继续从上次暂定时的值开始累加,依次循环,然后就得到了上面的运行结果
方案2
这个方案里我们要对方案1做一些改造,当有Producer生产出一个id时,直到有ConSumer来将他拿走,然后再生产下一个id,Consumer也是类似,直到Producer生产出id后才来取,否则一直等待下去
解决:
Object类里有个wait()方法和notify()/notifyAll(),一直很神秘,先前没怎么用过,看了一下原来这个东东是与线程同步操作密接相关的,
比如我们在应用中如果要对某一个方法或某个代码段做线程同步控制,那么需要为这个方法添加synchronized修饰或者是synchronized(obj){},这个obj可以理解成我们实际生活中房间的一把锁,默认情况下,当一个线程拥有了一个方法或代码段的锁后,就可以进入房间(方法或代码块)任意干坏事,而其他的线程只能在门外等待直到当前线程执行完毕打开房间(释放锁),而Object的wait和notify则是给这个锁提供了一些更先进的功能,这个锁可以自己开锁wait()(让同步方法或代码块暂时放弃占用的锁),进而让别的线程有机会进入运行,而当实际成熟时(满足运行的条件)又可以把自身锁住notify(),进而又继续进入上次被暂停的操作
改造后的TaskQueue2
- package com.crazycoder2010.thread;
- public class TaskQuery2 {
- private int id;
- private boolean valueSet;
- public synchronized int get(){
- if(!valueSet){
- try {
- wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- valueSet = false;
- notify();
- System.out.println("Got:"+this.id);
- return this.id;
- }
- public synchronized void put(int id){
- if(valueSet){
- try {
- wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- this.id = id;
- valueSet = true;
- System.out.println("Put:"+id);
- notify();
- }
- }
Producer,Consumer和Launcher类的代码不改动
运行结果如下
Put:0
Got:0
Put:1
Got:1
Put:2
Got:2
Put:3
Got:3
Put:4
Got:4
Put:5
Got:5
Put:6
Got:6
基于线程实现的生产者消费者模型(Object.wait(),Object.notify()方法)的更多相关文章
- 线程锁、threading.local(flask源码中用的到)、线程池、生产者消费者模型
一.线程锁 线程安全,多线程操作时,内部会让所有线程排队处理.如:list/dict/Queue 线程不安全 + 人(锁) => 排队处理 1.RLock/Lock:一次放一个 a.创建10个线 ...
- 锁丶threading.local丶线程池丶生产者消费者模型
一丶锁 线程安全: 线程安全能够保证多个线程同时执行时程序依旧运行正确, 而且要保证对于共享的数据,可以由多个线程存取,但是同一时刻只能有一个线程进行存取. import threading v = ...
- java多线程:线程间通信——生产者消费者模型
一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...
- 多线程学习-基础(十二)生产者消费者模型:wait(),sleep(),notify()实现
一.多线程模型一:生产者消费者模型 (1)模型图:(从网上找的图,清晰明了) (2)生产者消费者模型原理说明: 这个模型核心是围绕着一个“仓库”的概念,生产者消费者都是围绕着:“仓库”来进行操作, ...
- python网络编程--进程(方法和通信),锁, 队列,生产者消费者模型
1.进程 正在进行的一个过程或者说一个任务.负责执行任务的是cpu 进程(Process: 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在 ...
- 【Windows】用信号量实现生产者-消费者模型
线程并发的生产者-消费者模型: 1.两个进程对同一个内存资源进行操作,一个是生产者,一个是消费者. 2.生产者往共享内存资源填充数据,如果区域满,则等待消费者消费数据. 3.消费者从共享内存资源取数据 ...
- python2.0_s12_day9之day8遗留知识(queue队列&生产者消费者模型)
4.线程 1.语法 2.join 3.线程锁之Lock\Rlock\信号量 4.将线程变为守护进程 5.Event事件 * 6.queue队列 * 7.生产者消费者模型 4.6 queue队列 que ...
- 第23章 java线程通信——生产者/消费者模型案例
第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...
- 进程,线程,GIL,Python多线程,生产者消费者模型都是什么鬼
1. 操作系统基本知识,进程,线程 CPU是计算机的核心,承担了所有的计算任务: 操作系统是计算机的管理者,它负责任务的调度.资源的分配和管理,统领整个计算机硬件:那么操作系统是如何进行任务调度的呢? ...
随机推荐
- mdadm详细使用手册
1. 文档信息 当前版本 1.2 创建人 朱荣泽 创建时间 2011.01.07 修改历史 版本号 时间 内容 1.0 2011.01.07 创建<mdadm详细使用手册>1.0文档 1. ...
- [ASP.NET]uploadify简单使用讲解
背景:在用户控件中使用html 的file控件或者ASP.NET的FileUpLoad控件都无法获取到文件,于是想到听说过的uploadify uploadify官网:www.uploadify.co ...
- asp.net缓存使用介绍
介绍: 在我解释cache管理机制时,首先让我阐明下一个观念:IE下面的数据管理.每个人都会用不同的方法去解决如何在IE在管理数据.有的会提到用状态管理,有的提到的cache管理,这里我比较喜欢cac ...
- Microsoft.Office.Interop.Excel 导出Excel
; ; /// <summary> /// 使用 Excel.dll 导出 Excel /// </summary> /// <param name="list ...
- 浅析JS模块规范:AMD,CMD,CommonJS
from:https://www.jianshu.com/p/09ffac7a3b2c 随着JS模块化编程的发展,处理模块之间的依赖关系成为了维护的关键. 模块化 AMD,CMD,CommonJS ...
- select * 和 select 所有字段写出来 ,速度对比!
从很早时候,听老师说 select * from table 比 select a,b,c,d from table 要慢很多.3年来从未测试. 今天没事测一测, 不测不知道,一测吓一跳. 当然 ...
- C#winfrom最简DBHelp(数据库连接操作)
代码如下: //需引用命名空间 using System.Data; using System.Data.SqlClient; class DBHelper { //定义数据库连接语句,连接语句书写方 ...
- Python3.5 学习十一
关于消息队列 PY 中应用程序里对于消息队列的使用: 1 threading QUEUE 2 进程QUEUE 父进程.父进程下的子进程间进行交互 RabbitMQ 消息队列 :用于应用程序之间,可跨平 ...
- Python 断言 assert 的用法
assert 后边接的表达式的返回值必须是布尔值 assert expression, "对错误的描述信息" 如果expression表达式返回的是True, 程序正常执行, 如果 ...
- Linux 操作日志
这里: /var/log/messages