生产者与消费者模式

本文详细介绍了生产者消费者模式,包括其概念、利用通知机制和阻塞队列实现的示例,以及线程池如何运用该模式。通过对不同实现方式的分析,展示了如何平衡生产者和消费者的工作能力,提高程序效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、生产者与消费者

所谓生产者与消费者模式就是通过平衡生产线程与消费线程的工作能力来提高程序整体处理数据的速度…

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完才能继续生产数据,反之亦然,为了解决这种生产消费能力不均衡的问题,便有了生产者消费者模式。

二、利用通知机制实现生产者消费者模型

下面先从一个简单的例子入手:

package multithread;

public class ProduceAndConsume {
    private  boolean isProduce = false;
    
    private final Object LOCK = new Object();

    private volatile int i = 0; 

    public void produce() {
        synchronized (LOCK) {
            if (isProduce) {
                try {
                    LOCK.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println("produce => " + ++i);
                isProduce = true;
                LOCK.notify();
            }
        }
    }

    public void consume() {
        synchronized (LOCK) {
            if (!isProduce) {
                try {
                    LOCK.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println("consume => " + i);
                isProduce = false;
                LOCK.notify();
            }
        }
    }
    
    public static void main(String[] args) {
        ProduceAndConsume pc = new ProduceAndConsume();
        new Thread(() -> {
            while (true) {
                pc.produce();
            }
        }, "produce").start();

        new Thread(() -> {
            while (true) {
                pc.consume();
            }
        }, "consume").start();
    }
}

在这里插入图片描述

在这里插入图片描述

在上面的例子中我们分别启动了一个生产线程和一个消费线程,实现了一个最简单的生产者消费者模型,从截图中可以看出来,生产线程总是先生产一个数据,消费线程再去消费线程。但这里只是一个生产线程和一个消费线程,如果是多个线程这样的写法还行不行呢?下面改动实践一下:


    public static void main(String[] args) {
        ProduceAndConsume pc = new ProduceAndConsume();
        Stream.of("P1","P2").forEach( n -> {
            new Thread(() -> {
                while (true) {
                    pc.produce();
                }
            }, "produce").start();
        });

        Stream.of("C1","C2").forEach(n -> {
            new Thread(() -> {
                while (true) {
                    pc.consume();
                }
            }, "consume").start();
        });
    }

在这里插入图片描述
在这个我们分别用两个生产线程和两个消费线程,结果是生产消费了两次后就 block 住了,具体是什么原因呢?

根本原因是是 notify()在唤醒线程的时候唤醒的具体是哪个线程不能确定;那么这里的流程就有可能为: P1 线程生产一个数据以后进入 wait 状态,C1 线程消费一个数据之后唤醒 P1 线程并进入 wait 状态,之后 P2 线程生产一个数据之后唤醒 P1 线程并进入wait 状态,最后 C2 消费一个数据之后唤醒 P1 线程并进入 wait状态, P1 生产一个数据后唤醒了 P2,P2 被唤醒之后又进入 wait 状态。

最后第三个版本修改如下:

package multithread;

import java.util.stream.Stream;

public class ProduceAndConsume {
   private  boolean isProduce = false;
   
   private final Object LOCK = new Object();

   private volatile int i = 0; 

   public void produce() {
       synchronized (LOCK) {
           while (isProduce) {
               try {
                   LOCK.wait();
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
           
           System.out.println("produce => " + ++i);
           isProduce = true;
           LOCK.notifyAll();
           
       }
   }

   public void consume() {
       synchronized (LOCK) {
           while (!isProduce) {
               try {
                   LOCK.wait();
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
           
           System.out.println("consume => " + i);
           isProduce = false;
           LOCK.notifyAll();
       
       }
   }
   
   public static void main(String[] args) {
       ProduceAndConsume pc = new ProduceAndConsume();
       Stream.of("P1","P2").forEach( n -> {
           new Thread(() -> {
               while (true) {
                   pc.produce();
               }
           }, "produce").start();
       });

       Stream.of("C1","C2").forEach(n -> {
           new Thread(() -> {
               while (true) {
                   pc.consume();
               }
           }, "consume").start();
       });
   }
}

这里就是把 notify() 改为 notifyAll(),if 改为 while;改成 while 的原因是防止多个生产线程或者多个消费线程进入 wait 状态,每次只有一个线程可以获得监视器对象锁。

三、利用阻塞队列实现生产者消费者模型

生产者和消费者彼此之间不直接通信,而是通过阻塞队列来进行通信,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是从阻塞队列里面取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

实际上,阻塞队列也是通过使用通知模式来实现通信的,用 Lock 锁的多条件(Condition)阻塞控制。使用 BlockingQueue 封装了根据条件阻塞线程的过程,而我们就不用关心繁琐的 await / signal 操作了。

package multithread;

import java.util.concurrent.LinkedBlockingQueue;

public class ProduceAndConsume2 {

   private static LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(2);
   
   public static class Produce extends Thread{
       @Override
       public void run() {
           try {
               queue.put("product");
             
           } catch (Exception e) {
               e.printStackTrace();
           }
           System.out.println("produce...");
       }   
   }

   public static class Consume extends Thread{
       @Override
       public void run() {
           try {
              String product =  queue.take();
             
           } catch (Exception e) {
               e.printStackTrace();
           }
           System.out.println("consume...");
       }   
   }

   public static void main(String[] args) {
       for (int i = 0; i < 15; i++) {
           Produce produce = new Produce();
           produce.start();
       }

       for (int i = 0; i < 15; i++) {
           Consume consume = new Consume();
           consume.start();
       }
   }
}

四、线程池与生产者消费者模式

Java 中的线程池其实就是一种生产者和消费者模式的实现,生产者把任务丢给线程池,线程池创建处理任务,如果将要运行的任务大于线程池的核心线程数,那么就将任务扔到阻塞队列里,这种做法比只使用一个阻塞队列来实现生产者与消费者模式显然要高明得多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值