【多线程并发编程】十 面试官:生产者和消费者问题

前言
生产者和消费者问题,是面试过程中,经常问到的一个问题,本文,就来了解一下,生产者和消费者是怎么一回事。

1.概念
生产者消费者问题是一个著名的线程同步问题,也是我们面试过程中,经常会问到的一个经典面试题,生产者负责生产数据放到缓冲区,消费者负责从缓存中里面取。

注意这个缓冲区的大小是有上限的。

生产者发现供过于求,也就是说生产的速度太快了,也就是说,缓冲区里的数据,远远高于消费者的消费的能力,如果我们不做处理,就会造成速度的堆积,所以,该缓冲区是有上限的。可以理解为一个仓库,只能存放一定数据的产品,仓库都堆放满了,已经无地可放产品了,只能等消费者购买产品后,我们才能继续生产产品。

生产者发现供不应求,说明消费者消费的速度远远高于生产者的速度,这时候,我们就只能耐心的等待。应该避免库里已经没有产品了还在消费。
提到生产者和消费者,顿时,我们的脑海里面会浮现不少的产品,rabbitmq,kafka,dubbo等等。

2.模拟真实业务场景
深圳某充电桩厂家,有一个小库房,能存放10台充电桩,为了保证不必要的浪费,利益最大化,要求库房满了,就停止生产,也不能造成库存的数据远远小于消费的数量。

代码实战
通过synchronized+wait+notifyall实现
package com.cxyxs.thread.eleven;

/**

  • Description:

  • 转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/

  • Author: 程序猿学社

  • Date: 2020/2/29 10:25

  • Modified By:
    */
    public class Pile {
    //库存数量
    private int sum=0;
    private int max=10;

    /**

    • 生产者
      */
      public synchronized void pro(){
      while (sum == max){ //达到最大值说明库存已经满了,无法存放充电桩
      try {
      this.wait(); //需要等待消费者,消费后,才能生存
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      sum++;
      System.out.println(Thread.currentThread().getName()+":剩余充电桩数量为"+sum);
      this.notifyAll(); //通知消费者,我们库存有充电桩,赶紧来消费
      }

    /**

    • 消费者
      */
      public synchronized void consumer(){
      while (sum == 0){
      try {
      this.wait(); //说明库房,充电桩不够了,需要等待充电桩厂家,继续生产充电桩
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      sum --;
      System.out.println(Thread.currentThread().getName()+":剩余充电桩数量为"+sum);
      this.notifyAll(); //通知生产厂家,我已经消费了,你可以继续生产充电桩
      }
      }
      测试类

package com.cxyxs.thread.eleven;

/**

  • Description:synchronized+wait+notifyall解决生产者和消费者问题

  • 转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/

  • Author: 程序猿学社

  • Date: 2020/2/29 10:45

  • Modified By:
    */
    public class Demo1{
    public static void main(String[] args) {
    Pile pile = new Pile();

     for (int j = 0; j < 3; j++) {
         new Thread(()->{
             for (int i = 0; i < 15; i++) {
                 pile.pro();
             }
         },"生产者"+j).start();
    
    
         new Thread(()->{
             for (int i = 0; i < 15; i++) {
                 pile.consumer();
             }
         },"消费者"+j).start();
     }
    

    }
    }

通过测试结果,我们可以看出,不会存在生产超出库存最大的情况,也不会出现没有产品后还在消费的问题。

重点,注意判断是否进入等待的时候,使用while,而不是if。如果使用if,可能会存在虚假唤醒的问题,所以这里,使用while,再次判断,避免虚假唤醒的问题。

lock+await+signalAll
java1.5版本以后,引入新的三剑客,一代新人换旧人。我们来看一看1.5版本的三剑客都有哪些。

lock ----------synchronized

wait ---------- await

notifyall ----- signal

synchronized可以理解为自动挡,会自动释放锁
Lock可以理解为手动挡,这种方式虽说灵活,但是需要自己手动释放锁,为了防止死锁,我们lock一般与try finally配套使用。

package com.cxyxs.thread.eleven;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**

  • Description:Lock方式

  • 转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/

  • Author: 程序猿学社

  • Date: 2020/2/29 18:16

  • Modified By:
    */
    public class PileLock {
    //库存数量
    private int sum=0;
    private int max=10;

    final Lock lock = new ReentrantLock();
    private Condition pro= lock.newCondition();
    private Condition consumer= lock.newCondition();

    /**

    • 生产者
      */
      public void pro(){
      try {
      lock.lock(); //上锁
      while (sum == max){ //达到最大值说明库存已经满了,无法存放充电桩
      try {
      pro.await(); //需要等待消费者,消费后,才能生存
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      sum++;
      System.out.println(Thread.currentThread().getName()+":剩余充电桩数量为"+sum);
      pro.signalAll(); //通知消费者,我们库存有充电桩,赶紧来消费
      }finally {
      lock.unlock(); //重点
      }
      }

    /**

    • 消费者
      */
      public void consumer(){
      try {
      lock.lock(); //上锁
      while (sum == 0){
      try {
      consumer.await(); //说明库房,充电桩不够了,需要等待充电桩厂家,继续生产充电桩
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      sum --;
      System.out.println(Thread.currentThread().getName()+":剩余充电桩数量为"+sum);
      consumer.signalAll(); //通知生产厂家,我已经消费了,你可以继续生产充电桩
      } finally {
      lock.unlock(); //重点
      }
      }
      }
      测试类

package com.cxyxs.thread.eleven;

/**

  • Description:synchronized+wait+notifyall解决生产者和消费者问题

  • 转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/

  • Author: 程序猿学社

  • Date: 2020/2/29 10:45

  • Modified By:
    */
    public class Demo1{
    public static void main(String[] args) {
    // 通过synchronized+wait+notifyall实现
    // Pile pile = new Pile();
    //lock+await+signal
    PileLock pile = new PileLock();

     for (int j = 0; j < 3; j++) {
         new Thread(()->{
             for (int i = 0; i < 10; i++) {
                 pile.pro();
             }
         },"生产者"+j).start();
    
    
         new Thread(()->{
             for (int i = 0; i < 10; i++) {
                 pile.consumer();
             }
         },"消费者"+j).start();
     }
    

    }
    }

注意一定要用try finally,通过finally设置解锁,以确保在必要时释放锁定。

我们使用Lock来产生两个Condition对象来管理任务间的通信,一个是生产者,一个是消费者。

lock() 获取锁,unlock() 释放锁

await() 使当前线程加入 等待队列中,并释放当锁.当其他线程调用signal()会重新请求锁

signal会唤醒一个在 await()等待队列中的线程,signalAll会唤醒 await()等待队列中所有的线程

发布了0 篇原创文章 · 获赞 0 · 访问量 182

猜你喜欢

转载自blog.csdn.net/qq_41490913/article/details/104877898
今日推荐