java生产者与消费者模式
前言: 生产者和消费者模式是我们在学习多线程中很经典的一个模式,它主要分为生产者和消费者,分别是两个线程,
目录
一:生产者和消费者模式简介
二:生产者和消费者模式的实现
声明:本例来源于java经典著作:《Think in java》,接下来将会采用本例子将会借鉴其中的案例进行分析
首先我们来设想有一个这样的场景:一个饭店里有一个做饭的厨师和来吃饭的人,服务员负责端食物,这里就可以把厨师当做生产者,(这里暂且把服务员当做消费者),而食物则有这样的过程,被厨师生产出来,然后被服务员消费。当食物存在的时候,厨师等待,不再进行生产,服务员进行消费。当食物为空的时候,厨师开始生产食物,服务员等待。这中间就存在着一个线程之间协作的过程。我们来通过代码进行模拟:

首先是新建两个线程,一个厨师线程,一个服务员线程,双方进行协作:
以下是厨师线程:
public class Chef implements Runnable{ //product
private Restaurant restaurant;
private int count=0;
public Chef(Restaurant restaurant) {
this.restaurant = restaurant;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
while (restaurant.meal != null) {
wait();
}
if (++count==10){
System.out.println("out of food,closing");
restaurant.exec.shutdownNow();
}
System.out.println("Chef product meal"+count);
synchronized (restaurant.waitPerson){
restaurant.meal= new Meal(count);
restaurant.waitPerson.notifyAll();
}
TimeUnit.MICROSECONDS.sleep(100);
}
}
}catch (InterruptedException e){
System.out.println("Chef interruped");
}
}
}
以下是服务员线程:
public class WaitPerson implements Runnable{
private Restaurant restaurant;
public WaitPerson(Restaurant restaurant) {
this.restaurant = restaurant;
}
@Override
public void run() {
try{
while (!Thread.interrupted()) {
synchronized (this) {
while (restaurant.meal == null) {
wait();
}
System.out.println("waitPerson got:"+restaurant.meal);
}
synchronized (restaurant.chef){
restaurant.meal=null;
restaurant.chef.notifyAll();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("waitPerson interrupdate");
}
}
}
我们来分析一下厨师线程,主要看它的run方法,里面包含着一个try、catch块,首先它会一直捕获线程的状态,当它不处于interrupted(线程中断,此时无法运转)异常时,往下走。然后取得当前对象的锁,把它上锁,通过一个while循环,取得餐馆里面的meal,判断其是否为null.当餐馆里面的餐还有剩余的时候,此时生产者不需要工作,它处于wait状态,注意wait和Thread.sleep的区别,sleep方法运行的时候线程是不会丢弃锁的,而wait方法会释放锁,所以此时,被锁住的对象运行到wait方法,它已经释放了锁,因此消费者可以获取到锁。
再接着count会进行累加1。再接着锁住服务员线程,此时服务员线程获取锁的主动权,它通过notifyAll方法唤醒所有的在锁上等待线程,注意此处为什么是notifyAll而不是notify,这主要是因为此刻厨师线程并不知道等待的线程究竟是几条,为了线程安全起见,唤醒所有的等待线程。注意的是:notifyAll会唤醒所有线程,但是运行的只是其中一个,关于这个线程的筛选,是完全随机的。
唤醒了消费者线程,它就会运行到消费者线程,然后我们来看一下它的run方法,和之前的厨师线程差不多,依然是锁住当前的消费者线程,不过线程此时的条件变成了meal为null。接着再获取厨师的锁,使其meal为null,再唤醒厨师线程,接着程序就会运转到厨师线程,执行厨师的run方法。
再来看看Restaurant类:
public class Restaurant{
Meal meal;
ExecutorService exec =Executors.newCachedThreadPool();
WaitPerson waitPerson=new WaitPerson(this);
Chef chef=new Chef(this);
public Restaurant(){
exec.execute(chef);
exec.execute(waitPerson);
}
public static void main(){
new Restaurant();
}
}
这里创建了一个缓存线程池,然后在Restaurant的构造方法中开始执行线程命令,首先会从Chef开始生产,紧接着服务员消费,通过wait/notify进行线程之间的同步:
程序执行的结果:
Chef product meal1
waitPerson got:Meal1
Chef product meal2
waitPerson got:Meal2
Chef product meal3
waitPerson got:Meal3
Chef product meal4
waitPerson got:Meal4
Chef product meal5
waitPerson got:Meal5
Chef product meal6
waitPerson got:Meal6
Chef product meal7
waitPerson got:Meal7
Chef product meal8
waitPerson got:Meal8
Chef product meal9
waitPerson got:Meal9
out of food,closing
Chef product meal10
Chef interruped
waitPerson interrupdate
可以看到结果,线程有条不紊的运行,生产者每生产一个meal,发出order up信号,然后消费者消费这个meal,再轮到生产者,再到消费者,这就是生产者和消费者的模式的意义,它们之间同步工作,不会出现消费者线程没有meal的时候仍然去消费,生产者在有meal的时候依然去生产,这样就会产生线程安全的问题。
三:关于线程之间的通信
3.1:wait和notfiy方法

查询jdk可以看出,wait和notify方法都是object的方法,也就是说java所有的对象都可以继承这个方法。每个对象运行的时候都可以关联一个线程,调用wait方法就可以使当前线程处于等待状态,等待的时候它的它会释放掉锁,此时其它对象可以获取线程锁。而wait方法和notify是进行相互沟通的,只有notify/notifyAll方法才能唤醒被wait方法等待的线程,notify会唤醒这个线程。
注意看它的解释:唤醒在此对象监视器上等待的单个/所有线程,那么问题来了。监视器又是什么?
3.2:java监视器
3.2.1:对监视器的解释
监视器:monitor
锁:lock(JVM里只有一种独占方式的lock)
进入监视器:entermonitor
离开/释放监视器:leavemonitor
(entermonitor和leavemonitor是JVM的指令)
拥有者:owner
在JVM里,monitor就是实现lock的方式
entermonitor就是获得某个对象的lock(owner是当前线程)
leavemonitor就是释放某个对象的lock
3.2.2:通俗的解释
上面的这段话比较精简,其实理解起来很简单。有这样一句话:程序即生活,我们把它代入到生活实际中试衣间来思考一下,其实不难理解这段话。我们去一个商城购买衣服,选择好了,我们去试衣间,此刻好比对象entermonitor ,执行进入监视器这个指令,我们离开试衣间,就好比执行leavemonitor指令,这个monitor是一个狭小的空间,当一个对象进入的时候,其它对象就不能进入了。这其中的原理实现了队列等待和竞争的,线程进入会先排队,如果有线程进入监视器,当前线程就会等待,如果没有就直接进入,新来的线程会和已经等待的线程进行竞争,但是最后只有一个能进入,进入监视器的线程可以访问它的所有数据,包括类变量和实例变量。这也就实现了锁的原理。
3.3:线程协作解决的问题
3.3.1:线程数据访问的紊乱
假如没有协作的话,数据之间的访问一定会出现紊乱,比如生产者会可能在有很多meal的时候仍然会生产meal,消费者会在没有meal的时候依然进行消费,这都是我们在多线程的程序中不愿看到的,因为它会有很多不预期的事情发生
3.3.2:数据会错位
数据之间会错位,这体现在生产者会消费meal,而消费者会生产meal。此刻他们不能各自履行各自的职责,这也是我们极度不愿意看到的,而不采用保障措施就会有这样问题的出现。
可以看到
四:总结
本篇博客主要引用《Thinking in java》中的一段进行讲解,讲了线程之间的通信与协作的问题,如何合理运用wait和notify方法进行线程之间的通信,实现一个系统有条不紊的运行。这其中的最关键点在于理解锁的释放与获取的过程,以及线程的切换,生产者和消费者的相互沟通,协同工作的原理。
java生产者与消费者模式的更多相关文章
- Java并发编程(4)--生产者与消费者模式介绍
一.前言 这种模式在生活是最常见的,那么它的场景是什么样的呢? 下面是我假象的,假设有一个仓库,仓库有一个生产者和一个消费者,消费者过来消费的时候会检测仓库中是否有库存,如果没有了则等待生产,如果有就 ...
- Java多线程设计模式(2)生产者与消费者模式
1 Producer-Consumer Pattern Producer-Consumer Pattern主要就是在生产者与消费者之间建立一个“桥梁参与者”,用来解决生产者线程与消费者线程之间速度的不 ...
- java 线程并发(生产者、消费者模式)
线程并发协作(生产者/消费者模式) 多线程环境下,我们经常需要多个线程的并发和协作.这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”. Ø 什么是生产者? 生产者指的是负责生产数 ...
- java进阶(40)--wait与notify(生产者与消费者模式)
文档目录: 一.概念 二.wait的作用 三.notify的作用 四.生产者消费者模式 五.举例 ---------------------------------------分割线:正文------ ...
- Java生产者与消费者(上)
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 生产与消费者模式,是编程中最常用的模式之一,在多线程中应用比较明显.个人理解:在自助餐厅,厨师在不断 ...
- 使用libuv实现生产者和消费者模式
生产者和消费者模式(Consumer + Producer model) 用于把耗时操作(生产线程),分配给一个或者多个额外线程执行(消费线程),从而提高生产线程的响应速度(并发能力) 定义 type ...
- condition版生产者与消费者模式
1.简介 在爬虫中,生产者与消费者模式是经常用到的.我能想到的比较好的办法是使用redis或者mongodb数据库构造生产者消费者模型.如果直接起线程进行构造生产者消费者模型,线程容易假死,也难以构造 ...
- Java生产者与消费者(下)
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 上一讲我们让消费者和生产者都各停1毫秒,实际上大多并不是这样的.第二讲,我们讲一个极端的例子和一个正 ...
- 【爬虫】Condition版的生产者和消费者模式
Condition版的生产者和消费者模式 threading.Condition 在没有数据的时候处于阻塞状态,有数据可以使用notify的函数通知等等待状态的线程运作 threading.Condi ...
随机推荐
- GO开发[五]:golang结构体struct
Go结构体struct Go语言的结构体(struct)和其他语言的类(class)有同等的地位,但Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性 ...
- Html5+js测试题(开发版)
------------------------------------------------ 1. 谈谈你对js闭包的理解: 使用闭包主要是为了设计私有的方法和变量.闭包的优点是可以避免全局变量的 ...
- geotools实现多边形的合并&缓冲区
这算是第一次接触开源工具包,说实话刚开始有点不知所措,中途遇到很多问题的时候也感觉头皮发麻,不过很高兴自己还是坚持下来了. geotools就不做过多的介绍了,想总结一下如何根据开源内容做自己的项目. ...
- asp.net -mvc框架复习(8)-实现用户登录模型部分的编写
1.配置文件添加数据库连接字符串(web.config) 2.编写通用数据库访问类 (1)引入命名空间 using System.Configuration; (2) 定义连接字符串 (3)编写完成 ...
- Centos6.9安装部署nginx服务器
(一)依赖包安装 首先,gcc,pcre,zlib,openssl安装一边(可以用非-devel,但是嫌麻烦....用非-devel的看这个链接) yum -y install gcc ------ ...
- Do you know how many stuff inside your Google Account?
My friend Sandy she wants me to do her a favor. She'd like to clear Hangouts chat history. I think s ...
- Springmvc 并发访问的线程安全性问题
首先对于spring的IOC来说,对象是由Spring来帮我们管理,也就是在Spring启动的时候,在Spring容器中,由Spring给我们创建的,Spring会帮我们维护,一般都是单例的,也就是一 ...
- sersync+rsync同步
(一)目标服务器(rsync):10.60.50.192 1.安装rsync yum -y install rsync 2.创建rsyncd.conf配置文件 vi /etc/rsyncd.conf ...
- jsp页面从标签属性中获取值
你还可以在"data-*" 属性里使用json语法,例如 <div id="awesome-json" data-awesome='{"game ...
- 自己写的日志框架--linkinLog4j--框架可配置+提性能
OK,上一篇博客我们已经实现了日志框架的基本的功能,但是还有一个最大的问题就是日志输出地不能重定向,然后一些输出也不可控.那现在我们来实现一个比较完整的日志框架. 设计思路如下: 1,定义一堆常量Li ...