基于线程实现的生产者消费者模型(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是计算机的核心,承担了所有的计算任务: 操作系统是计算机的管理者,它负责任务的调度.资源的分配和管理,统领整个计算机硬件:那么操作系统是如何进行任务调度的呢? ...
随机推荐
- EasyUI 让dialog中的treegrid的列头固定
先上效果: 最主要是在treegrid要加上"fit:true "如果不加那么就会用diglog的滚动条,导致treegrid的头就没办法固定. Code<div id=&q ...
- Spring Boot 应用系列 1 -- Spring Boot 2 整合Spring Data JPA和Druid,双数据源
最近Team开始尝试使用Spring Boot + Spring Data JPA作为数据层的解决方案,在网上逛了几圈之后发现大家并不待见JPA,理由是(1)MyBatis简单直观够用,(2)以Hib ...
- asp.net—工厂模式
一.什么是工厂模式 定义:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类. 二.怎么使用工厂模式 首先模拟一个场景:有一个汽车工厂, 可以日本车.美国车.中国车... 这个场景怎么用工厂 ...
- 为控件动态添加Style
此文可解决: 重写控件时,给控件加入子控件或父控件的样式切换问题. 很灵活的可以根据不同内容显示不同样式 子控件作用在: <DataTemplate x:Key="ColmunHea ...
- sql表与表之间的数据操作
--把一张表的内容更新到另一张表 update 表1 set 表1.Store=t2.Name from 表2 t2 where 表1.id=t2.id --备份一张表 create table ta ...
- WinForm ListView不分页加载大量数据
WinForm的ListView在加载大量数据时会出现闪烁的问题,同时数据加载很慢.如果你的列表中有超过千条的数据且不做特殊处理还是用普通的ListView.Items.Add(),估计你的用户得抱怨 ...
- .NET Core中使用Dapper操作Oracle存储过程最佳实践
为什么说是最佳实践呢?因为在实际开发中踩坑了,而且发现网上大多数文章给出的解决方法都不能很好地解决问题.尤其是在获取类型为OracleDbType.RefCursor,输出为:ParameterDir ...
- 微信小程序 Unexpected end of JSON input/Unexpected token o in JSON at position 1
原因JSON.parse无法识别某些url中的特殊字符,所以报错 mistakes.js中 nextBtn:function(){ var nextData = this.data.dataNextI ...
- 微信小程序redirect 到tab不刷新
// 更新2018/11/20:现在小程序的页面栈长度为10 更正 2018/11/20: 经过一段时间的实践,我发现以前方法存在很多问题,比如 getCurrentPages 方法并不在官方的 AP ...
- 【JS深入学习】——函数创建和重载
今天做一个关注/取消的功能,由于需要向后台发送请求,想通过控制用户点击发送的频次减少不必要的请求,即在一定时间内,用户点击多次但只发送一次数据,自然而然想到了使用[函数节流]. function th ...