多线程等待唤醒机制之生产消费者模式

 上篇楼主说明了多线程中死锁产生的原因并抛出问题——死锁的解放方案,那么在本篇文章,楼主将引用一个KFC生产汉堡,顾客购买汉堡的过程来说明死锁解决方案及多线程的等待唤醒机制。

简单地用一幅图来说明KFC生产汉堡,顾客来消费的过程:

场景分析:

  1. 资源类:Hamburger
  2. 设置汉堡数据:SetThread(生产者)
  3. 获取汉堡数据:GetThread(消费者)
  4. 测试类:HamburgerTest
  5. 不同种类的线程(生产者、消费者)针对同一资源(汉堡)的操作
  6. 当汉堡有存货的时候,汉堡师傅不再生产,顾客可消费;反之,汉堡师傅生产,顾客不可消费
  7. 是否有线程安全问题?当然。楼主在《线程安全问题》那篇文章给出了判定方式,在该场景全部满足。

代码构建:类里面的i属性是楼主为了效果好一些特意加的,与本文要说明的问题无关;

  首先是资源类Hamburger.java,楼主这里为了模拟只简单的构造了3个字段,其中flag用来表示资源是否有数据。

 1 package com.jon.hamburger; 2  3 public class Hamburger { 4     private String name;//汉堡名称 5     private double price;//汉堡价格 6     private boolean flag;//汉堡是否有数据的标志,默认为false,表示没有数据 7     public String getName() { 8         return name; 9     }10     public void setName(String name) {11         this.name = name;12     }13     public double getPrice() {14         return price;15     }16     public void setPrice(double price) {17         this.price = price;18     }19     public boolean isFlag() {20         return flag;21     }22     public void setFlag(boolean flag) {23         this.flag = flag;24     }25     26 }

  接着是生产者SetThread.java与GetThread.java,都需要实现Runnable接口。场景分析中的第7点已经说明,场景存在线程安全的问题,楼主在前篇文章已经说明,线程安全的问题可以通过加锁来进行解决,但是这里涉及到不同种类的线程,所以必须要满足2点:

  1. 不同种类的线程都要加锁
  2. 不同种类的线程加的锁必须是同一把

SetThread.java

 1 package com.jon.hamburger; 2  3 public class SetThread implements Runnable { 4     private Hamburger hamburger; 5     private int i; 6  7     public SetThread(Hamburger hamburger) { 8         this.hamburger = hamburger; 9     }10     @Override11     public void run() {12         while (true) {//为了数据效果好一些,楼主加入了判断13             synchronized (hamburger) {14                 if(this.hamburger.isFlag()){//如果有存货15                     try {16                         hamburger.wait();//线程等待17                     } catch (InterruptedException e) {                        
18                         e.printStackTrace();19                     }20                 }21                 //如果没有存货,这模拟生产22                 if (i % 2 == 0) {23                     this.hamburger.setPrice(25.0);24                     this.hamburger.setName("俊锅的汉堡");25                 } else {26                     this.hamburger.setPrice(26.0);27                     this.hamburger.setName("大俊锅的汉堡");28                 }29                 this.hamburger.setFlag(true);//生产完成后更改标志30                 hamburger.notify();//唤醒当前等待的线程31                 i++;//只为数据效果好一些,无实际意义32             }33 34         }35 36     }37 38 }

GetThread.java

 1 package com.jon.hamburger; 2  3 public class GetThread implements Runnable { 4  5     private Hamburger hamburger; 6     /** 7      * 为了让同步锁使用同一个对象锁,这里通过构造方法进行传递 8      * @param hamburger 9      */10     public GetThread(Hamburger hamburger){11         this.hamburger = hamburger;12     }13     @Override14     public void run() {15         while(true){16             synchronized (hamburger) {17                 if(!this.hamburger.isFlag()){//如果没有存货,线程等待18                     try {19                         hamburger.wait();20                     } catch (InterruptedException e) {                        
21                         e.printStackTrace();22                     }23                 }24                 //如果有数据则进行输出25                 System.out.println(this.hamburger.getName()+"-----"+this.hamburger.getPrice());26                 this.hamburger.setFlag(false);//更改标志27                 hamburger.notify();//唤醒线程28             }    
29         }        
30         31     }32 33 }

  可以看到两个线程类的run方法中都使用了sysnchronized进行了加锁,并使用同一个hamburger对象锁。

  再看测试类HamburgerTest.java及输出:

 1 package com.jon.hamburger; 2  3  4  5 public class HamburgerTest { 6  7  8     public static void main(String[] args) { 9         Hamburger hamburger = new Hamburger();10         11         SetThread st = new SetThread(hamburger);//通过构造方法传入共享资源数据hamburger12         GetThread gt = new GetThread(hamburger);13         14         Thread td1 = new Thread(st);15         Thread td2 = new Thread(gt);16         17         td1.start();18         td2.start();19 20     }21 22 }

  测试类中,我们通过构造方法给SetThread和GetThread传入了同一个对象,以保证锁对象为同一把。

  输出结果,线程间不相互影响,同时都无NULL------0.0的情况输出:

  

1 俊锅的汉堡-----25.02 大俊锅的汉堡-----26.03 俊锅的汉堡-----25.04 大俊锅的汉堡-----26.05 俊锅的汉堡-----25.06 大俊锅的汉堡-----26.07 俊锅的汉堡-----25.08 大俊锅的汉堡-----26.0

代码分析:

  我们假设线程t2先抢到CPU的执行权,那么程序执行流程可用下图表示:

  根据程序代码分析也可见,由于线程之间相互等待产生的死锁问题也得以解决,解决方案就是通过唤醒。另外,文本楼主还使用了另一种方式,思路也差不多,示例代码与本文的示例代码放在一起,已上传到GitHub。

时间: 2024-10-09 02:45:55

多线程等待唤醒机制之生产消费者模式的相关文章

day11(多线程,唤醒机制,生产消费者模式,多线程的生命周期)

A:进程: 进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. B:线程: 线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序. C:简而言之: 一个程序运行后至少有一个进程,一个进程中可以包含多个线程 线程实现 实现的两种方式 继承Thread public class MyThread extends Thread{ @Ove

多线程---等待唤醒机制

线程执行当中,线程是放在线程池中的. 线程运行当中通常使用了wait()方法等待的话,再使用notify()唤醒线程,通常唤醒的是线程池中等待的第一个线程. 而用notifyAll()则是唤醒全部线程 . 以上三种红字的方法: wait().notify().notifyAll()必须使用在同步synchronized中 因为对持有监视器(锁)的线程才能操作. 注意: 这三种方法 wait().notify().notifyAll()是定义在java.lang.Object这个父类当中的,线程是

java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

 *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时候才能消费,仓空则等待. *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产. *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费. 下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费. 生产一个资源,就得消费一个资源. 代码如下: pub

JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

JAVA之旅(十四)--静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制 JAVA之旅,一路有你,加油! 一.静态同步函数的锁是class对象 我们在上节验证了同步函数的锁是this,但是对于静态同步函数,你又知道多少呢? 我们做一个这样的小实验,我们给show方法加上static关键字去修饰 private static synchronized void show() { if (tick > 0) { try { Thread

多线程的等待唤醒机制之消费者和生产者模式

/** * 等待唤醒之生产者和消费者模型 * 生成者: 创建和添加数据的线程 * 消费者: 销毁和删除数据的线程 * 问题1: 生成者生成数据过快, 消费者消费数据相对慢,不接收数据了, 会造成数据丢失 * 问题2: 消费者消费数据过快, 生成者生成数据相对慢,不发送数据了, 会造成数据被重复读取 * 总结 : 生产数据过快会造成数据丢失 * 生成数据过慢会造成数据被重复读取 * * * wait()和notifyAll()用法: * 1. 必须是同一个对象的wait()和和notifyAll(

java多线程中的生产者与消费者之等待唤醒机制@Version1.0

一.生产者消费者模式的学生类成员变量生产与消费demo,第一版1.等待唤醒:    Object类中提供了三个方法:    wait():等待    notify():唤醒单个线程    notifyAll():唤醒所有线程2.为什么这些方法不定义在Thread类中呢?  这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象.  所以,这些方法必须定义在Object类中.3.当我们在使用多线程的时候有的时候需要,一条线程产生一个数据,另一条线程接着消费一个数据,一边生产一边消费,

Android-Java多线程通讯(生产者 消费者)&10条线程对-等待唤醒/机制的管理

上一篇博客 Android-Java多线程通讯(生产者 消费者)&等待唤醒机制 是两条线程(Thread-0 / Thread-1) 在被CPU随机切换执行: 而今天这篇博客是,在上一篇博客Android-Java多线程通讯(生产者 消费者)&等待唤醒机制 的基础上,扩大规模增加10条线程去执行 生产者 消费者: 注意:?? 上一篇博客是两条线程在执行(生产者 消费者)例如:当Thread-0 锁.wait(); 等待 冻结后,  Thread-1 锁.notify(); 唤醒的一定是 T

【转】通过生产者消费者案例理解等待唤醒机制和虚假唤醒

首先引入下面这段生产者和消费者的程序,店员类作为生产产品和消费产品的中介,其中的数据product为共享数据,产品最多只能囤积5个,当产品达到5个还在生产时,就会提示"产品已满!",类似地,如果产品只有0个了还在消费,会提示"缺货!": 1 package concurrent; 2 3 //店员类 4 class Clerk { 5 private int product = 0; 6 7 // 进货 8 public synchronized void get(

Android(java)学习笔记71:生产者和消费者之等待唤醒机制

首先我们根据梳理我们之前Android(java)学习笔记70中关于生产者和消费者程序思路: 下面我们就要重点介绍这个等待唤醒机制: 第一步:还是先通过代码体现出等待唤醒机制 package cn.itcast_05; /* * 分析: * 资源类:Student * 设置学生数据:SetThread(生产者) * 获取学生数据:GetThread(消费者) * 测试类:StudentDemo * * 问题1:按照思路写代码,发现数据每次都是:null---0 * 原因:我们在每个线程中都创建了