JAVA多线程设计模式篇 6、Producer-Consumer 模式——你生产我消费

实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。大概的结构如下图。

  • 生产者
    数据的提供方可形象地称为数据的生产者,它“生产”了数据,

  • 消费者
    而数据的加工方则相应地被称为消费者,它“消费”了数据。实际上,生产者“生产”数据的速率和消费者“消费”数据的速率往往是不均衡的,比如数据的“生产”要比其“消费”快。为了避免数据的生产者和消费者中处理速率快的一方需要等待处理速率慢的一方,

  • 缓冲区
    Producer-Consumer模式通过在数据的生产者和消费者之间引入一个通道(Channel,暂时可以将其简单地理解为一个队列)对二者进行解耦(Decoupling):生产者将其“生产”的数据放入通道,消费者从相应通道中取出数据进行“消费”(处理),生产者和消费者各自运行在各自的线程中,从而使双方处理速率互不影响。

1.示例

1.1 定义生产者

class Producer extends Thread {
    private Buffer buffer;
    private int number;

    public Producer(Buffer b, int number) {
        buffer = b;
        this.number = number;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                // 模拟生产数据
                sleep(500);
            } catch (InterruptedException e) {
            }
            
            // 将数据放入缓冲区
            buffer.put(i);
            System.out.println("生产者 #" + this.number + " put: " + i);
        }
    }
}

1.2 定义消费者

class Consumer extends Thread {
    private Buffer buffer;
    private int number;

    public Consumer(Buffer b, int number) {
        buffer = b;
        this.number = number;
    }

    public void run() {
        int value;
        for (int i = 0; i < 10; i++) {
            // 从缓冲区中获取数据
            value = buffer.get();
            try {
                // 模拟消费数据
                sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("消费者 #" + this.number + " got: " + value);
        }
    }
}

1.3 定义缓冲区

class Buffer {
    private List<Integer> data = new ArrayList<>();
    private static final int MAX = 10;
    private static final int MIN = 0;

    public synchronized int get() {
        while (MIN == data.size()) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        Integer i = data.remove(0);
        notifyAll();
        return i;
    }
    
    public synchronized void put(int value) {
        while (MAX == data.size()) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        data.add(value);
        notifyAll();
    }
}

2.模式优点

Producer-Consumer模式使得“产品”的生产者和消费者各自的处理能力(速率)相对来说互不影响。生产者只需要将其“生产”的“产品”放入通道中就可以继续处理,而不必等待相应的“产品”被消费者处理完毕。而消费者运行在其自身的工作者线程中,它只管从通道中取“产品”进行处理,而不必关心这些“产品”由谁“生产”以及如何“生产”这些细节。因而消费者的处理能力相对来说又不影响生产者,同时又与生产者是松耦合(Loose Coupling)的关系。另一方面,当消费者处理能力比生产者处理能力大的时候,可能出现通道为空的情形,此时消费者的工作者线程会被暂挂直到生产者“生产”了新的“产品”。此时出现了事实上的消费者等待生产者的情形。类似地,当消费者的处理能力小于生产者的处理能力时,通道可能会满,导致生产者线程被暂挂直到消费者“消费”了通道中的部分“产品”而腾出了存储空间。此时出现了事实上的生产者等待消费者的情形。生产者和消费者各自的处理能力相互不影响是相对的。

  • 并发 (异步)
    生产者直接调用消费者,两者是同步(阻塞)的,如果消费者吞吐数据很慢,这时候生产者白白浪费大好时光。而使用这种模式之后,生产者将数据丢到缓冲区,继续生产,完全不依赖消费者,程序执行效率会大大提高。

  • 解耦
    生产者和消费者之间不直接依赖,通过缓冲区通讯,将两个类之间的耦合度降到最低。

  • 削峰填谷
    缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。

总结

Producer-Consumer模式可以看作模式的模式,即许多模式可以看作该模式的一个实例,

java.util.concurrent.ThreadPoolExecutor可以看成是Producer-Consumer模式的可复用实现。ThreadPoolExecutor内部维护的工作队列和工作者线程相当于Producer-Consumer模式的Channel参与者和Consumer参与者。而ThreadPoolExecutor的客户端代码则相当于Producer参与者。利用ThreadPoolExecutor实现Producer-Consumer模式,

多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。

https://github.com/forestnlp/concurrentlab

如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。

您的支持是对我最大的鼓励。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
生产者-消费模式是一种经典的多线模式,其中一个或多个生产者生成数据并将其放入缓冲区,而一个或多个消费者从缓冲区中读取数据并进行处理。下面是一个使用Python实现的生产者-消费模式的简单例子: ```python import threading import queue import time # 定义一个缓冲区 buffer = queue.Queue(maxsize=10) # 生产线程函数 def producer(): while True: # 生产一个数据 data = time.time() # 将数据放入缓冲区 buffer.put(data) print("Producer: produced item %s" % data) # 等待一段时间 time.sleep(1) # 消费线程函数 def consumer(): while True: # 从缓冲区中取出一个数据 data = buffer.get() print("Consumer: consumed item %s" % data) # 处理数据 # ... # 通知缓冲区数据已经被处理 buffer.task_done() # 创建生产者和消费线程 producer_thread = threading.Thread(target=producer) consumer_thread = threading.Thread(target=consumer) # 启动线程 producer_thread.start() consumer_thread.start() # 等待所有线程结束 producer_thread.join() consumer_thread.join() ``` 在这个例子中,我们使用了Python内置的`queue`模块来实现缓冲区。首先,我们创建了一个`Queue`对象作为缓冲区,并设置了最大容量为10。然后,我们定义了生产者和消费线程函数,分别用于生成数据和处理数据。在生产线程中,我们使用`put`方法将数据放入缓冲区。在消费线程中,我们使用`get`方法从缓冲区中取出数据,并使用`task_done`方法通知缓冲区数据已经被处理。 最后,我们创建生产者和消费线程,并启动它们。在主线程中,我们使用`join`方法等待所有线程结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值