基于线程实现的生产者消费者模型(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是计算机的核心,承担了所有的计算任务: 操作系统是计算机的管理者,它负责任务的调度.资源的分配和管理,统领整个计算机硬件:那么操作系统是如何进行任务调度的呢? ...
随机推荐
- KbmMW安装
系统环境及相关软件版本: Windows 7 64位, Delphi XE Version 15.0.3953.35171 , Indy 10.5.7 kbmMW4.90.04 , kbmMemTab ...
- Tomcat监听443端口的方法
当我们需要更安全的访问网站的时候就会选择使用https协议,而https协议默认的端口号为443端口,这就是我们为什么向让Tomcat监听在443端口的原因,因为监控在非80端口和443端口的web服 ...
- EasyUI 让dialog中的treegrid的列头固定
先上效果: 最主要是在treegrid要加上"fit:true "如果不加那么就会用diglog的滚动条,导致treegrid的头就没办法固定. Code<div id=&q ...
- NavigationViewController页面间通信及传值
使用进行页面跳转时,应该使用方法来跳转至下一页面,这样的话,下一页面同样在容器中. 1AloneSetPrizeViewController *setPrize = [[AloneSetPrizeVi ...
- C# 实现图片压缩
代码: private static ImageCodecInfo GetImageCodecInfo(ImageFormat imageFormat) { ImageCodecInfo[] imag ...
- 什么是Ajax?Ajax的原理是什么?Ajax的核心技术是什么?Ajax的优缺点是什么?
Ajax是一种无需重新加载整个网页,能够更新部分网页的技术.Asynchronous JavaScript and XML的缩写,是JavaScript.XML.CSS.DOM等多个技术的组合. Aj ...
- Android逆向系列文章— Android基础逆向(6)
本文作者:HAI_ 0×00 前言 不知所以然,请看 Android逆向-Android基础逆向(1) Android逆向-Android基础逆向(2) Android逆向-Android基础逆向(2 ...
- Flask从入门到精通之使用Flask-SQLAlchemy管理数据库
Flask-SQLAlchemy 是一个Flask 扩展,简化了在Flask 程序中使用SQLAlchemy 的操作.SQLAlchemy 是一个很强大的关系型数据库框架,支持多种数据库后台.SQLA ...
- Flask从入门到精通之静态文件
Web 程序不是仅由Python 代码和模板组成.大多数程序还会使用静态文件,例如HTML代码中引用的图片.JavaScript 源码文件和CSS. 在前面的章节中,我们曾检查hello.py 程序的 ...
- hexo安装总结
博客原文地址:Claiyre的个人博客 如需转载,请在文章开头注明原文地址 hexo真心是一个不错的东西呢,安装简单(然而对博主来说并不是这样,伤心脸),主题样式简洁优雅,还有多种选择. 流程如下 安 ...