【多线程并发编程】十一 生产者和消费者问题(面试必问)

程序猿学社的GitHub,欢迎Star
https://github.com/ITfqyd/cxyxs
本文已记录到github,形成对应专题。

前言

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

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()等待队列中所有的线程
发布了286 篇原创文章 · 获赞 557 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/qq_16855077/article/details/104569388