producer-consumer design pattern

Introduction

Through the study of the previous articles, I think you have thoroughly mastered the wait()methods and notify()how to use them and under what circumstances. In this article, we will explain the producer-consumer pattern in the design pattern. I will write it by hand. A code of the producer-consumer pattern to explain. Only after learning and comprehending this design pattern can you truly understand 消息中间件the underlying ideas of implementation, such as Kafka, RocketMQ, RabbitMQ, etc.

Suggested collection:

For a series of tutorials about synchronized关键字, wait(), and notify()methods, please refer to the following articles:

"Thread-safe and thread-unsafe analysis and examples in Java"

"Two Ways of Creating Threads in Java Official Documents and Analysis of Advantages and Disadvantages"

"How to stop threads correctly in Java (three scenarios)"

Producer-Consumer Patterns of Design Patterns

"Java multi-threaded wait() and notify() series method usage tutorial"

"The use of notifyAll() method in Java multithreading tutorial"

"Java two threads alternately print odd and even numbers (two methods comparison)"

"Two Ways and Principles of Synchronized Implementing Class Locks in Java"

"Two ways and principle analysis of synchronized implementation of object lock in Java"

"Analysis and Code Verification of Synchronized Reentrancy and Uninterruptibility in Java"

"Eight usage scenarios of Java multi-threaded access to Synchronized synchronization methods"

1. Producer-consumer model

Although the producer-consumer pattern is not one of the common design patterns, it is also a commonly used and efficient programming pattern in programming. The so-called design patterns are nothing more than easy-to-use "routines" practiced by our predecessors. We can learn from them, and we can avoid detours and achieve more with less effort.

The producer is where data is produced, and the consumer is where data is consumed. Through this model, the two can effectively reduce coupling and separate producers and consumers, thereby reducing marginal costs, saving resources, and improving work efficiency.

common example

consumer

Just like online shopping, as consumers, we don’t need to know all the sellers. Consumers have purchase needs, open the shopping APP, and place an order if they can find what they need. . We don't need to keep in touch with all the factories, we just need to keep an eye on the items we like.

producer

Also as a seller, we don’t need to know consumers all over the world. The seller can take pictures and package the prepared products, and synchronize the product information to the shopping APP. He does not need to ask unfamiliar customers one by one if they need it. If necessary, you only need to wait for the service when the customer consults.

buffer

The shopping APP is a buffer that connects producers and consumers. The goods in the buffer are deposited by sellers, and consumers can choose and buy the goods they need in the buffer.

With the above basic understanding, let's take a look at implementing a producer-consumer pattern with Java code.

2. Code implementation

1. Producer

Code description: The producer is only responsible for producing the product, and then putting the product into the buffer queue Queue.

Resolution: The producer only needs to know the product, and then send the product to the cache queue, it does not need to know the consumer. The code is very simple to read and easy to maintain.

class Producer implements Runnable {
	private Queue queue;

	public Producer(Queue queue) {
		this.queue = queue;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			queue.put();
		}
	}
}

2. Consumers

Code description: The consumer is only responsible for taking out the product from the cache queue Queuefor use.

Resolution: Consumers do not need to understand and associate consumers, consumers are only associated with cache queues. In this way, the code of the consumer is also very concise, it does not need to be related to the logic of the producer to produce the product, and it does not need to directly contact the producer.

class Consumer implements Runnable {
	private Queue queue;

	public Consumer(Queue queue) {
		this.queue = queue;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			queue.take();
		}
	}
}

3. Cache queue

Code description: The cache queue Queueneeds to provide two interfaces, one is to allow producers to store products in the queue. Another interface is to allow consumers to get products from the queue.

Analysis: The core responsibilities of the cache queue are two: access and retrieval. Very concise in terms of design thinking.

put() method: This method must be a synchronous method. If there are multiple producers, it can ensure that the cache queue will not be burst (the number of products stored by multiple threads is greater than the maximum value).

Why add while(MAX_VALUE == list.size())judgment, can it be replaced if语句? ?
Answer: It cannot be replaced if语句. The meaning of this line is that when the cache queue reaches the upper limit of the maximum value, it is forbidden to store products. while(条件不满足){wait()}This is to prevent the thread from being spuriously awakened.

虚假唤醒:一个线程可以从挂起状态直接变为可运行状态,即时没有被其他线程调用wait()、notify()系列方法、或者被中断、或者等待超时。

False wakeup rarely occurs, but to prevent it, the solution is to constantly test whether the conditions for the thread to be awakened are met, wait if not, and exit the loop only when the wakeup conditions are met.

In this example, the put() and take() methods both add code to prevent false wake-up, that is, if the conditions are not met, you can continue to deposit or withdraw products, otherwise it will wait.

class Queue {

	//每次生产最大容量
	private Integer MAX_VALUE = 10;
	private LinkedList<String> list = new LinkedList<>();
	
	// 入库方法:达到最大值后停止生产
	public synchronized void put() {
		while (MAX_VALUE == list.size()) {
			try {
			    //执行到此处时,下面的list.add()将不被执行,直到不满足循环条件
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		list.add(UUID.randomUUID().toString());
		System.out.println("生产出:" + list.size() + "个宝贝");
		notify();
	}

	// 出库:库存为0后,停止消费
	public synchronized void take() {

		while (0 == list.size()) {
			try {
			      //执行到此处时,下面的list.poll()将不被执行,直到不满足循环条
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("消费:" + list.poll() + ",剩余宝贝数量:" + list.size());
		notify();
	}

}

4. Client use

The client only needs to create a cache queue, and create a producer and a consumer to run in one thread. It is also very friendly to use on the client side, no complicated configuration is required, and it is very organized. This is the role of design patterns, and this is also the experience of our predecessors, so we have to stand on the shoulders of giants.

public class ProducerConsumerPattern {

	public static void main(String[] args) {
		Queue queue = new Queue();
		Thread consumer = new Thread(new Consumer(queue));
		Thread producer = new Thread(new Producer(queue));
		producer.start();
		consumer.start();
	}
}

Summarize

This paper implements and analyzes the consumer and producer patterns through wait()and methods. This pattern elegantly realizes the decoupling of producers and consumers, and supports concurrency. One producer can support multiple consumers. notify()A consumer can also support multiple producers. System resources can also be used reasonably. When there are many tasks for producers and few tasks for consumers, the system can allocate more resources to producers, and vice versa. If you like this series of tutorial articles, please like, follow and bookmark.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324133513&siteId=291194637