zoukankan      html  css  js  c++  java
  • 多线程等待唤醒机制之生产消费者模式

      上篇楼主说明了多线程中死锁产生的原因并抛出问题——死锁的解放方案,那么在本篇文章,楼主将引用一个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     @Override
    11     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     @Override
    14     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);//通过构造方法传入共享资源数据hamburger
    12         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.0
    2 大俊锅的汉堡-----26.0
    3 俊锅的汉堡-----25.0
    4 大俊锅的汉堡-----26.0
    5 俊锅的汉堡-----25.0
    6 大俊锅的汉堡-----26.0
    7 俊锅的汉堡-----25.0
    8 大俊锅的汉堡-----26.0

    代码分析:

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

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

      本文示例代码地址:https://github.com/LJunChina/JavaResource  中的Hamburger

    本文为博主原创文章,转载请在明显位置注明出处: http://www.cnblogs.com/qq503665965/
  • 相关阅读:
    解决Firefox下outerHTML不支持问题
    神奇的css属性pointerevents
    IE6 double marginleft Bug
    解决IE低版本不支持call和apply问题
    JavaScript函数参数的可修改性
    IE6/7 double paddingbottom Bug
    各浏览器对document.getElementById等方法的实现差异
    JavaScript中两种类型的全局对象/函数
    JavaScript子类用Object.getPrototypeOf去调用父类方法
    JavaScript声明全局变量三种方式的异同
  • 原文地址:https://www.cnblogs.com/qq503665965/p/6533655.html
Copyright © 2011-2022 走看看