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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程等待唤醒机制之生产消费者模式相关的知识,希望对你有一定的参考价值。

 上篇楼主说明了多线程中死锁产生的原因并抛出问题——死锁的解放方案,那么在本篇文章,楼主将引用一个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。


以上是关于多线程等待唤醒机制之生产消费者模式的主要内容,如果未能解决你的问题,请参考以下文章

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

多线程之Java中的等待唤醒机制

java 22 - 17 多线程之等待唤醒机制(接16)

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

多线程之Condition的await和signalsignalAll等待/通知机制

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