23.生产与消费模型
实际生活中,需要操作共享的某个资源(水池),但是对这个共享资源的操作方式不同(部分是注水[生产]、部分是抽水[消费])。把这种现象我们可以称为生产和消费模型。
生产:它可以采用部分线程进行模拟。多个线程同时给水池中注水。
消费:它可以采用部分线程进行模拟。多个线程同时从水池中抽水。
对资源的不同的操作方式,每种方式都可以让部分的线程去负责。多个不同的线程,他们对相同的资源(超市、水池等)操作方式不一致。
这个时候我们不能使用一个run方法对线程的任务进行封装。所以这里就需要定义不同的线程任务类,描述不同的线程的任务。
通过不同的线程操作,来控制同一个资源,这种现象就属于生产消费模型
例:
1.创建公共资源类
package com.weiwei.test;/*** 公共资源类**/public class Resource {/*** 生产资源方法*/public void add(){}/*** 消费资源方法*/public void delete(){}}
2.创建生产任务类和消费任务类
/*** 生产任务类线程**/public class ShengChanThread implements Runnable{//定义被控制的资源private Resource resource=null;//构造方法初始化public ShengChanThread(Resource resource){this.resource= resource;}public void run(){for(int i=1;i<=20;i++){resource.add();}} }/*** 消费任务类线程**/public class XiaoFeiThread implements Runnable{//定义被控制的资源private Resource resource=null;//构造方法初始化public XiaoFeiThread(Resource resource){this.resource= resource;}public void run(){for(int i=1;i<=20;i++){resource.delete();}} }
3.给资源类添加方法
/*** 公共资源类**/public class Resource {//使用一个对象数组充当水池private Object obj[]=new Object[1];//生产一个消费后,才能再生产//记录生产和消费的次数private int num=1;/*** 生产资源方法*/public void add(){obj[0]="水资源=="+num;System.out.println(Thread.currentThread().getName()+"生产的资源是:"+obj[0]);num++;}/*** 消费资源方法*/public void delete(){System.out.println(Thread.currentThread().getName()+"消费的资源是:"+obj[0]);obj[0]=null;}}
测试类:
public class Main1 {public static void main(String[] args) {Resource r = new Resource();ShengChanThread sc = new ShengChanThread(r);Thread scth1 = new Thread(sc);//Thread scth2 = new Thread(sc);scth1.setName("生产者1");scth1.start();XiaoFeiThread xf = new XiaoFeiThread(r);Thread xfth1 = new Thread(xf);//Thread xfth2 = new Thread(xf);xfth1.setName("消费者1");xfth1.start();}}
有时会出现消费者消费为null情况:
以上图为例,过程是生产线程执行到4的时候,切换给消费线程,还没来得急打印输出“消费者生产的资源是”,线程又切换给了生产线程,执行到6,切换到消费线程,继续执行没执行完的那句话,输出消费资源4,还没执行对数组赋值null,又切换给了生产线程执行生产7,然后切换回消费线程,执行赋值null,然后继续执行消费线程,就会输出消费资源为null。
有时会出现生产者生产为null情况:
假设CPU在消费者线程上,那么消费者正要打印了消费为null的情况下,还没有将数组空间赋值为null之前,CPU切换到生产者,生产者将水注到数组空间中之后,还没有打印,CPU又切回到消费者线程上,消费者线程就会将数组空间立刻赋值为null。CPU如果再切回到生产者线程上,打印出来的消费就是null。
上面的这两个问题就是因为当前线程正在访问的共享资源的时候,其他的线程也可以访问共享资源所产生的。所以线程操作共享数据时,需要进行线程同步。
线程同步能够保证生产的时候不能消费,或者消费的时候不能生产。
4.使用synchronized同步代码块进行线程同步
//创建一个同步对象private static final Object loc = new Object();/*** 生产资源方法*/public void add(){synchronized (loc) {obj[0]="水资源=="+num;System.out.println(Thread.currentThread().getName()+"生产的资源是:"+obj[0]);num++;}}/*** 消费资源方法*/public void delete(){synchronized (loc) {System.out.println(Thread.currentThread().getName()+"消费的资源是:"+obj[0]);obj[0]=null;}
上面执行完成以后发现存在新的问题,出现多次消费没有生产或者多次生产没有消费的问题,要解决这个问题,首先需要判断是否满足消费或者生产的条件。
什么时候消费:当数组空间中不是null的时候可以进行消费。
什么时候生产:当数组空间为null的时候才能生产。
如果不满足生产的时候,但是当前正好CPU在生产的线程上,这时必须要让注水的线程等待,等到可以注水的时候将本次注水的动作做完。
如果不满足消费的时候,但是当前正好CPU在消费的线程上,这时必须要让抽水的线程等待,等到可以抽水的时候将本次抽水的动作做完。
使用java中线程的等待和唤醒机制(线程间的通信)。
5.等待和唤醒机制----修改Resource为注水和抽水方法添加线程等待和唤醒操作
等待和唤醒的方法没有定义在Thread类中,而是定义在Object类中(因为只有同步的锁才能让线程等待或者将等待的线程唤醒,而同步的锁是任意对象,等待和唤醒的方法只能定义在Object类中)
void |
wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待 |
void |
notify() 唤醒在此对象监视器上等待的单个线程。 |
void |
notifyAll() 唤醒在此对象监视器上等待的所有线程。 |
注意:等待和唤醒(线程通信)必须位于同步中。因为等待和唤醒必须使用当前的锁才完成。
/*** 生产资源方法**/public void add() throws InterruptedException{synchronized (loc) {//如果对象数组不为空,不能生产if(objs[0]!=null){//生产等待loc.wait();//抛出异常}objs[0]="水资源=="+num;System.out.println(Thread.currentThread().getName()+"生产的资源是:"+objs[0]);num++;//唤醒抽水消费线程loc.notify();}}/*** 消费资源方法**/public void delete() throws InterruptedException{synchronized (loc) {//如果对象数组为空,不能消费if(objs[0]==null){//消费等待loc.wait();//抛出异常}System.out.println(Thread.currentThread().getName()+"消费的资源是:"+objs[0]);objs[0]=null;//唤醒注水生产线程loc.notify();}
测试类:
上面程序处理好了单线程的注水和抽水动作,下面将程序修改为多线程注水和多线程抽水情况。
6.修改主类,增加注水和抽水线程---多线程生产和消费
public class Main1 {public static void main(String[] args) {Resource r = new Resource();ShengChanThread sc = new ShengChanThread(r);Thread scth1 = new Thread(sc);Thread scth2 = new Thread(sc);scth1.setName("生产者1");scth2.setName("生产者2");scth1.start();scth2.start();XiaoFeiThread xf = new XiaoFeiThread(r);Thread xfth1 = new Thread(xf);Thread xfth2 = new Thread(xf);xfth1.setName("消费者1");xfth2.setName("消费者2");xfth1.start();xfth2.start();}}
测试:
问题出现了:多次生产和多次消费!!
原因:假设生产线程1完成后,切换给其他线程,这个时候可以切换的线程有三个,生产线程1,消费线程1,2。因此会出现多次消费或者多次生产。
解决方式:将if语句改为while语句,如果消费线程中数组为null,则让他一直等待不往下进行消费,直到切换到生产线程进行生产,使得数组中不为null,此时如果切回到消费线程,则进行消费。生产同理。
7.将if语句改为while语句—解决多次生产和多次消费
/*** 生产资源方法**/public void add() throws InterruptedException{synchronized (loc) {//如果对象数组不为空,不能生产while(objs[0]!=null){//生产等待loc.wait();//抛出异常}objs[0]="水资源=="+num;System.out.println(Thread.currentThread().getName()+"生产的资源是:"+objs[0]);num++;//唤醒抽水消费线程loc.notify();}}/*** 消费资源方法**/public void delete() throws InterruptedException{synchronized (loc) {//如果对象数组为空,不能消费while(objs[0]==null){//消费等待loc.wait();//抛出异常}System.out.println(Thread.currentThread().getName()+"消费的资源是:"+objs[0]);objs[0]=null;//唤醒注水生产线程loc.notify();} }
此时经过测试发现新的问题,有一种情况。我设置的到20,此处到3就结束了。
所有的线程都处于等待了,外面没有可以执行的线程了,这个时候出现死锁。
死锁过程(以上图为例):起初数组为null,进入生产线程1,生产水资源1,然后切换给消费者2,进行消费后,数组为空了,切换给消费者1,此时消费者1处于等待,直到切换给生产者2,生产者生产后数组不为null,切换给了自己生产者2 ,此时生产者2处于等待,直到切换给消费者2,消费后数组为null,同样切换给自己,此时消费者2处于等待,直到切换给生产者1,生产后数组不为null后同样切换给自己生产者1,此时生产者1处于等待。到此四条线程全部处于等待状态。
解决方案:将notify()换成notifyAll(),唤醒所有处于等待的线程。每次在唤醒的时候都是唤醒所有线程,即使唤醒了自己的同伴,也无所谓,因为还要继续判断,这样一定还会等待,但是唤醒中一定有另外一方的线程,它们肯定不会等待。它们不等待,就会去操作,它们操作完成也唤醒所有。
8. 将notify()换成notifyAll()---解决死锁状态
/*** 生产资源方法**/public void add() throws InterruptedException{synchronized (loc) {//如果对象数组不为空,不能生产while(objs[0]!=null){//生产等待loc.wait();//抛出异常}objs[0]="水资源=="+num;System.out.println(Thread.currentThread().getName()+"生产的资源是:"+objs[0]);num++;//唤醒抽水消费线程loc.notifyAll();}}/*** 消费资源方法**/public void delete() throws InterruptedException{synchronized (loc) {//如果对象数组为空,不能消费while(objs[0]==null){//消费等待loc.wait();//抛出异常}System.out.println(Thread.currentThread().getName()+"消费的资源是:"+objs[0]);objs[0]=null;//唤醒注水生产线程loc.notifyAll();} }
多生产多消费的程序中,为了保证不出现全部线程被wait的情况,只能在唤醒的时候使用notifyAll将所有处于等待的线程唤醒。这样每次都可以保证一定会有存活的线程。但是这种唤醒效率太低了,经常会发生生产方唤醒自己的同伴线程,或者是消费方唤醒自己的同伴线程。
在JDK5中提供Condition接口。它用来代替等待和唤醒机制。
9.Condition接口---解决唤醒效率低
在JDK5之前,一个同步的锁下面的等待和唤醒无法辨别当前让等待或唤醒的线程到底属于生产还是属于消费。而Condition接口,它可以创建出不同的等待和唤醒的对象,然后可以用在不同的场景下:
可以创建一个Condition对象,专门负责生产。
可以创建一个Condition对象,专门负责消费。
可以通过负责生产的Condition对象专门监视负责生产的线程。通过负责消费的Condition监视消费的线程。等待和唤醒的时候,可以使用各自的Condition对象。
void |
await()造成当前线程在接到信号或被中断之前一直处于等待状态。 |
void |
signal()唤醒一个等待线程。 |
void |
signalAll() 唤醒所有等待线程。 |
注意:
如果使用Condition接口,同步必须使用Lock接口。
如果程序中同步使用的同步代码块,等待和唤醒只能使用Object中的wait、notify、notifyAll方法。
只有同步使用的Lock接口,等待和唤醒才能使用Condition接口。
10.修改线程同步,使用Lock接口
import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/*** 公共资源类**/public class Resource {// 使用一个对象数组充当水池private Object objs[] = new Object[1];// 生产一个消费后,才能再生产// 记录生产和消费的次数private int num = 1;// 创建Lock接口,作为同步的锁private Lock lock = new ReentrantLock();// 负责监视生产的线程private Condition scCondition = lock.newCondition();// 负责监视消费的线程private Condition xfCondition = lock.newCondition();/*** 生产资源方法**/public void add() {try {// 锁住资源lock.lock();// 如果对象数组不为空,不能生产while (objs[0] != null) {// 生产等待scCondition.await();}objs[0] = "水资源==" + num;System.out.println(Thread.currentThread().getName() + "生产的资源是:" + objs[0]);num++;// 唤醒抽水消费线程xfCondition.signal();} catch (Exception e) {e.printStackTrace();} finally {// 释放锁lock.unlock();}}/*** 消费资源方法**/public void delete() {try {// 锁住资源lock.lock();// 如果对象数组为空,不能消费while (objs[0] == null) {// 消费等待xfCondition.await();}System.out.println(Thread.currentThread().getName() + "消费的资源是:" + objs[0]);objs[0] = null;// 唤醒注水生产线程scCondition.signal();} catch (Exception e) {e.printStackTrace();} finally {// 释放锁lock.unlock();}}}
有无异常都要释放锁,因此改为try…catch…finally
11.各种区别
11.1 等待和唤醒两种方式的区别
Object类提供的wait、notify、notifyAll方法 |
Condition接口提供的await(),signal(),signalAll() |
同步代码【synchronized】实现线程同步 |
Lock接口对象实现线程同步 |
效率低,会唤醒同类 |
效率高,不唤醒同类 |
11.2 notify和notifyAll区别
notify |
notifyAll |
只随机唤醒一个wait线程 |
唤醒所有的wait线程 |
可能会导致死锁 |
不会导致死锁 |
唤醒等待的线程不分彼此 |
11.3 signal和signalAll区别
signal |
signalAll |
只随机唤醒一个wait线程【同一类】 |
唤醒所有的wait线程【同一类】 |
11.4 sleep和wait区别
sleep |
wait |
|
Thread |
Object |
|
依赖于系统时钟和CPU调度机制 |
线程调用notify()或者notifyAll()方法 |
|
不释放以获取的锁资源 |
释放以获取的锁资源 |
23.生产与消费模型相关推荐
- 多线程-单生产单消费模型
2019独角兽企业重金招聘Python工程师标准>>> 创建资源对象,提供保存和取出方法(使用synchronized代码块实现) /*** Created by shaoqingh ...
- python生产和消费模型_python queue和生产者和消费者模型
queue队列 当必须安全地在多个线程之间交换信息时,队列在线程编程中特别有用. classqueue.Queue(maxsize=0) #先入先出classqueue.LifoQueue(maxsi ...
- Java实现生产消费模型的5种方式
** 前言 ** 生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储 ...
- 生产-消费模型之阻塞队列的源码分析
文章目录 前言 阻塞队列API 存放元素 boolean add(E e) boolean offer(E e) boolean offer(E e, long timeout, TimeUnit u ...
- java生产消费模型代码实现_生产者-消费者模型的Java实现
本文转自:http://tanlan.iteye.com/blog/1158154 生产者-消费者(producer-consumer)问题,也称作有界缓冲区(bounded-buffer)问题,两个 ...
- 【专题介绍】视频内容生产与消费创新(Part2)
" "音视频+无限可能"是一扇 LiveVideoStackCon面向新兴领域开启的大门,在移动互联网红利消失.内卷的局面下,智能车.制造.金融.医疗.出海等新兴领域还在 ...
- Kafka2.12安装与配置/生产与消费
Kafka2.12安装与配置/生产与消费 一.Kafka安装与配置 1.1 Java环境为前提 jdk下载地址链接:jdk1.8 提取码:9plz zookeeper下载地址链接:zookeeper3 ...
- 深入理解生产则消费者模型
文章目录 经典并发同步模式:生产者-消费者设计模式 什么是生产者-消费者模式 为什么要使用生产者消费者模式 生产者-消费者模式的特点 生产者-消费者模式的应用场景 生产者-消费者模式的优点 生产者-消 ...
- kafka java_Kafka 使用Java实现数据的生产和消费demo
前言 在上一篇中讲述如何搭建kafka集群,本篇则讲述如何简单的使用 kafka .不过在使用kafka的时候,还是应该简单的了解下kafka. Kafka的介绍 Kafka是一种高吞吐量的分布式发布 ...
最新文章
- LeetCode简单题之二进制矩阵中的特殊位置
- ActiveMQ—Queue与Topic区别
- 教学案例 计算机,计算机教学案例及反思
- SDK安装报错HTTP Status 416
- 去广告,原来可以如此简单——ADSafe 3.5.4.520 精简版
- VC问题 IntelliSense:“没有可用的附加信息”,[请參见“C++项目 IntelliSense 疑难解答”,获得进一步的帮助]...
- 常见食物营养成分表图_营养成分表,你会看么?
- python连接mysql用哪个模块_pymysql模块使用---Python连接MySQL数据库
- NSUserDefaults使用
- Power BI 与 Azure Analysis Services 的数据关联:4、Power BI 连接到Azure Analysis Services 并展示...
- WPF 美化界面,样式的使用
- 451.根据字符出现频率排序
- linux大华客户端,rdesktop 下载-rdesktop(Linux下远程桌面客户端) 1.8.2 官方版 - 河东下载站...
- 【全志V3s / LicheePi Zero / 荔枝派】Air724合宙4G模块RNDIS拨号
- Chia命令行P图工具
- 如何搭建一个会员网站?零基础用WordPress做一个会员网站视频教程
- 从移动互联网时代到物联网时代 梆梆安全追寻“本质安全”
- codeforces#375(div.2)723D - Lakes in Berland dfs+bfs
- [含论文+源码等]微信小程序居家养老+后台管理系统[包运行成功]
- 求职信英语计算机作文,英语求职信范文(精选5篇)