• Java之多线程的生产者消费者问题的详细解析


    3.生产者消费者

    3.1生产者和消费者模式概述【应用】

    • 概述

      生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。

      所谓生产者消费者问题,实际上主要是包含了两类线程:

      一类是生产者线程用于生产数据

      一类是消费者线程用于消费数据

      为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

      生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

      消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

    • Object类的等待和唤醒方法

      方法名说明
      void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
      void notify()唤醒正在等待对象监视器的单个线程
      void notifyAll()唤醒正在等待对象监视器的所有线程

    3.2生产者和消费者案例【应用】

    • 案例需求

      • 桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量

      • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

        1.判断是否有包子,决定当前线程是否执行

        2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子

        3.生产包子之后,更新桌子上包子状态,唤醒消费者消费包子

      • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

        1.判断是否有包子,决定当前线程是否执行

        2.如果没有包子,就进入等待状态,如果有包子,就消费包子

        3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子

      • 测试类(Demo):里面有main方法,main方法中的代码步骤如下

        创建生产者线程和消费者线程对象

        分别开启两个线程

    • 代码实现

      1. public class Desk {
      2.    //定义一个标记
      3.    //true 就表示桌子上有汉堡包的,此时允许吃货执行
      4.    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
      5.    public static boolean flag = false;
      6.    //汉堡包的总数量
      7.    public static int count = 10;
      8.    //锁对象
      9.    public static final Object lock = new Object();
      10. }
      11. public class Cooker extends Thread {
      12. //   生产者步骤:
      13. //           1,判断桌子上是否有汉堡包
      14. //   如果有就等待,如果没有才生产。
      15. //           2,把汉堡包放在桌子上。
      16. //           3,叫醒等待的消费者开吃。
      17.    @Override
      18.    public void run() {
      19.        while(true){
      20.            synchronized (Desk.lock){
      21.                if(Desk.count == 0){
      22.                    break;
      23.               }else{
      24.                    if(!Desk.flag){
      25.                        //生产
      26.                        System.out.println("厨师正在生产汉堡包");
      27.                        Desk.flag = true;
      28.                        Desk.lock.notifyAll();
      29.                   }else{
      30.                        try {
      31.                            Desk.lock.wait();
      32.                       } catch (InterruptedException e) {
      33.                            e.printStackTrace();
      34.                       }
      35.                   }
      36.               }
      37.           }
      38.       }
      39.   }
      40. }
      41. public class Foodie extends Thread {
      42.    @Override
      43.    public void run() {
      44. //       1,判断桌子上是否有汉堡包。
      45. //       2,如果没有就等待。
      46. //       3,如果有就开吃
      47. //       4,吃完之后,桌子上的汉堡包就没有了
      48. //               叫醒等待的生产者继续生产
      49. //       汉堡包的总数量减一
      50.        //套路:
      51.            //1. while(true)死循环
      52.            //2. synchronized 锁,锁对象要唯一
      53.            //3. 判断,共享数据是否结束. 结束
      54.            //4. 判断,共享数据是否结束. 没有结束
      55.        while(true){
      56.            synchronized (Desk.lock){
      57.                if(Desk.count == 0){
      58.                    break;
      59.               }else{
      60.                    if(Desk.flag){
      61.                        //有
      62.                        System.out.println("吃货在吃汉堡包");
      63.                        Desk.flag = false;
      64.                        Desk.lock.notifyAll();
      65.                        Desk.count--;
      66.                   }else{
      67.                        //没有就等待
      68.                        //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
      69.                        try {
      70.                            Desk.lock.wait();
      71.                       } catch (InterruptedException e) {
      72.                            e.printStackTrace();
      73.                       }
      74.                   }
      75.               }
      76.           }
      77.       }
      78.   }
      79. }
      80. public class Demo {
      81.    public static void main(String[] args) {
      82.        /*消费者步骤:
      83.        1,判断桌子上是否有汉堡包。
      84.        2,如果没有就等待。
      85.        3,如果有就开吃
      86.        4,吃完之后,桌子上的汉堡包就没有了
      87.                叫醒等待的生产者继续生产
      88.        汉堡包的总数量减一*/
      89.        /*生产者步骤:
      90.        1,判断桌子上是否有汉堡包
      91.        如果有就等待,如果没有才生产。
      92.        2,把汉堡包放在桌子上。
      93.        3,叫醒等待的消费者开吃。*/
      94.        Foodie f = new Foodie();
      95.        Cooker c = new Cooker();
      96.        f.start();
      97.        c.start();
      98.   }
      99. }

    3.3生产者和消费者案例优化【应用】

    • 需求

      • 将Desk类中的变量,采用面向对象的方式封装起来

      • 生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用

      • 创建生产者和消费者线程对象,构造方法中传入Desk类对象

      • 开启两个线程

    • 代码实现

      1. public class Desk {
      2.    //定义一个标记
      3.    //true 就表示桌子上有汉堡包的,此时允许吃货执行
      4.    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
      5.    //public static boolean flag = false;
      6.    private boolean flag;
      7.    //汉堡包的总数量
      8.    //public static int count = 10;
      9.    //以后我们在使用这种必须有默认值的变量
      10.   // private int count = 10;
      11.    private int count;
      12.    //锁对象
      13.    //public static final Object lock = new Object();
      14.    private final Object lock = new Object();
      15.    public Desk() {
      16.        this(false,10); // 在空参内部调用带参,对成员变量进行赋值,之后就可以直接使用成员变量了
      17.   }
      18.    public Desk(boolean flag, int count) {
      19.        this.flag = flag;
      20.        this.count = count;
      21.   }
      22.    public boolean isFlag() {
      23.        return flag;
      24.   }
      25.    public void setFlag(boolean flag) {
      26.        this.flag = flag;
      27.   }
      28.    public int getCount() {
      29.        return count;
      30.   }
      31.    public void setCount(int count) {
      32.        this.count = count;
      33.   }
      34.    public Object getLock() {
      35.        return lock;
      36.   }
      37.    @Override
      38.    public String toString() {
      39.        return "Desk{" +
      40.                "flag=" + flag +
      41.                ", count=" + count +
      42.                ", lock=" + lock +
      43.                '}';
      44.   }
      45. }
      46. public class Cooker extends Thread {
      47.    private Desk desk;
      48.    public Cooker(Desk desk) {
      49.        this.desk = desk;
      50.   }
      51. //   生产者步骤:
      52. //           1,判断桌子上是否有汉堡包
      53. //   如果有就等待,如果没有才生产。
      54. //           2,把汉堡包放在桌子上。
      55. //           3,叫醒等待的消费者开吃。
      56.    @Override
      57.    public void run() {
      58.        while(true){
      59.            synchronized (desk.getLock()){
      60.                if(desk.getCount() == 0){
      61.                    break;
      62.               }else{
      63.                    //System.out.println("验证一下是否执行了");
      64.                    if(!desk.isFlag()){
      65.                        //生产
      66.                        System.out.println("厨师正在生产汉堡包");
      67.                        desk.setFlag(true);
      68.                        desk.getLock().notifyAll();
      69.                   }else{
      70.                        try {
      71.                            desk.getLock().wait();
      72.                       } catch (InterruptedException e) {
      73.                            e.printStackTrace();
      74.                       }
      75.                   }
      76.               }
      77.           }
      78.       }
      79.   }
      80. }
      81. public class Foodie extends Thread {
      82.    private Desk desk;
      83.    public Foodie(Desk desk) {
      84.        this.desk = desk;
      85.   }
      86.    @Override
      87.    public void run() {
      88. //       1,判断桌子上是否有汉堡包。
      89. //       2,如果没有就等待。
      90. //       3,如果有就开吃
      91. //       4,吃完之后,桌子上的汉堡包就没有了
      92. //               叫醒等待的生产者继续生产
      93. //       汉堡包的总数量减一
      94.        //套路:
      95.            //1. while(true)死循环
      96.            //2. synchronized 锁,锁对象要唯一
      97.            //3. 判断,共享数据是否结束. 结束
      98.            //4. 判断,共享数据是否结束. 没有结束
      99.        while(true){
      100.            synchronized (desk.getLock()){
      101.                if(desk.getCount() == 0){
      102.                    break;
      103.               }else{
      104.                    //System.out.println("验证一下是否执行了");
      105.                    if(desk.isFlag()){
      106.                        //有
      107.                        System.out.println("吃货在吃汉堡包");
      108.                        desk.setFlag(false);
      109.                        desk.getLock().notifyAll();
      110.                        desk.setCount(desk.getCount() - 1);
      111.                   }else{
      112.                        //没有就等待
      113.                        //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
      114.                        try {
      115.                            desk.getLock().wait();
      116.                       } catch (InterruptedException e) {
      117.                            e.printStackTrace();
      118.                       }
      119.                   }
      120.               }
      121.           }
      122.       }
      123.   }
      124. }
      125. public class Demo {
      126.    public static void main(String[] args) {
      127.        /*消费者步骤:
      128.        1,判断桌子上是否有汉堡包。
      129.        2,如果没有就等待。
      130.        3,如果有就开吃
      131.        4,吃完之后,桌子上的汉堡包就没有了
      132.                叫醒等待的生产者继续生产
      133.        汉堡包的总数量减一*/
      134.        /*生产者步骤:
      135.        1,判断桌子上是否有汉堡包
      136.        如果有就等待,如果没有才生产。
      137.        2,把汉堡包放在桌子上。
      138.        3,叫醒等待的消费者开吃。*/
      139.        Desk desk = new Desk();
      140.        Foodie f = new Foodie(desk);
      141.        Cooker c = new Cooker(desk);
      142.        f.start();
      143.        c.start();
      144.   }
      145. }

    3.4阻塞队列基本使用【理解】

    • 阻塞队列继承结构

    • 常见BlockingQueue:

      ArrayBlockingQueue: 底层是数组,有界

      LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值

    • BlockingQueue的核心方法:

      put(anObject): 将参数放入队列,如果放不进去会阻塞

      take(): 取出第一个数据,取不到会阻塞

    • 代码示例

      1. public class Demo02 {
      2.    public static void main(String[] args) throws Exception {
      3.        // 创建阻塞队列的对象,容量为 1
      4.        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(1);
      5.        // 存储元素
      6.        arrayBlockingQueue.put("汉堡包");
      7.        // 取元素
      8.        System.out.println(arrayBlockingQueue.take());
      9.        System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞
      10.        System.out.println("程序结束了");
      11.   }
      12. }

    3.5阻塞队列实现等待唤醒机制【理解】

    • 案例需求

      • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

        1.构造方法中接收一个阻塞队列对象

        2.在run方法中循环向阻塞队列中添加包子

        3.打印添加结果

      • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

        1.构造方法中接收一个阻塞队列对象

        2.在run方法中循环获取阻塞队列中的包子

        3.打印获取结果

      • 测试类(Demo):里面有main方法,main方法中的代码步骤如下

        创建阻塞队列对象

        创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象

        分别开启两个线程

    • 代码实现

      1. public class Cooker extends Thread {
      2.    private ArrayBlockingQueue bd;
      3.    public Cooker(ArrayBlockingQueue bd) {
      4.        this.bd = bd;
      5.   }
      6. //   生产者步骤:
      7. //           1,判断桌子上是否有汉堡包
      8. //   如果有就等待,如果没有才生产。
      9. //           2,把汉堡包放在桌子上。
      10. //           3,叫醒等待的消费者开吃。
      11.    @Override
      12.    public void run() {
      13.        while (true) {
      14.            try {
      15.                bd.put("汉堡包");
      16.                System.out.println("厨师放入一个汉堡包");
      17.           } catch (InterruptedException e) {
      18.                e.printStackTrace();
      19.           }
      20.       }
      21.   }
      22. }
      23. public class Foodie extends Thread {
      24.    private ArrayBlockingQueue bd;
      25.    public Foodie(ArrayBlockingQueue bd) {
      26.        this.bd = bd;
      27.   }
      28.    @Override
      29.    public void run() {
      30. //       1,判断桌子上是否有汉堡包。
      31. //       2,如果没有就等待。
      32. //       3,如果有就开吃
      33. //       4,吃完之后,桌子上的汉堡包就没有了
      34. //               叫醒等待的生产者继续生产
      35. //       汉堡包的总数量减一
      36.        //套路:
      37.        //1. while(true)死循环
      38.        //2. synchronized 锁,锁对象要唯一
      39.        //3. 判断,共享数据是否结束. 结束
      40.        //4. 判断,共享数据是否结束. 没有结束
      41.        while (true) {
      42.            try {
      43.                String take = bd.take();
      44.                System.out.println("吃货将" + take + "拿出来吃了");
      45.           } catch (InterruptedException e) {
      46.                e.printStackTrace();
      47.           }
      48.       }
      49.   }
      50. }
      51. public class Demo {
      52.    public static void main(String[] args) {
      53.        ArrayBlockingQueue bd = new ArrayBlockingQueue<>(1);
      54.        Foodie f = new Foodie(bd);
      55.        Cooker c = new Cooker(bd);
      56.        f.start();
      57.        c.start();
      58.   }
      59. }

  • 相关阅读:
    常用傅里叶变换表
    DLMS/COSEM中的信息安全:安全密钥(中)续2
    Helm3模板-控制流程
    高通骁龙8cx Gen3和骁龙8cx gen2差距
    Web3和低代码开发:下一代Web应用开发的合作与创新
    实现0.5px的线
    java+python+nodejs设备配件采购报修进销存管理系统
    乾元通多卡聚合路由器的技术解析
    element-ui对话框dialog详解
    nginx+HTTPS证书
  • 原文地址:https://blog.csdn.net/qq_69748833/article/details/133420648