Life is too short to have a dog

One, foreword

We looked at the rationale and usage scenarios for the Observer pattern in the last article, Design Patterns in Java (I) : The Observer Pattern. In today’s article, we’ll do a little more extensibility — compare the producer-consumer pattern to the Observer pattern.

What is the producer-consumer model?

Unlike the observer pattern, the producer-consumer pattern itself does not belong to any of the design patterns. So what exactly is the producer-consumer model? Here’s an example:

As shown in the figure above, producers and consumers are just like the contributors and readers of a magazine. The same magazine can have multiple contributors and its readers can also have multiple, and the magazine is the bridge (that is, the buffer) between the authors and readers. Through the magazine data buffer, the author can deliver the completed works to the readers who have subscribed to the magazine. In this process, the author does not need to care whether the readers have received the works or completed the reading. The author and the reader are two relatively independent objects, and their behaviors do not affect each other.

As you can see, there are three roles in this example: producer, consumer, and buffer. Producers and consumers are better understood. The former is the production data, while the latter processes the data produced by the former. In the producer-consumer pattern, the buffer plays a role of decoupling, supporting asynchronous, and supporting the busy/idle imbalance.

Three, the difference between the two

1. Different programming paradigm

Producers – consumers mode and the observer pattern first differences above have already said, the former is a kind of process oriented software design pattern, do not belong to the Gang of Four of any one of 23 design patterns, while the latter is one of the 23 design patterns, which is one of the object-oriented design patterns.

2. Different associations

This conceptual difference leads to the next difference, which is that in the observer model there is only a one-to-many relationship, not a many-to-many relationship, whereas in the producer-consumer model there is a many-to-many relationship.

In observer mode, there is only one observed, but there can be many observers. For example, at a traffic light at an intersection, vehicles going straight will only observe the traffic light that controls going straight, not the traffic light that controls turning left or right. That is to say, the object of observation is fixed and unique.

In the producer-consumer model, however, there can be multiple producers and multiple consumers. In this case, the reader only cares about the content of the magazine and does not care who the author is, and the author only needs to know that the work can be published in the corresponding magazine, not who the readers will be.

3. Different coupling relationships

From the previous difference, it is not difficult to see that the producer-consumer pattern and the observer pattern have different coupling relationships, the former is light coupling, the latter is heavy coupling.

4. Different application scenarios

The observer pattern is mostly used in event-driven models, while the producer-consumer pattern is mostly used in inter-process communication for decoupling and concurrent processing. The producer-consumer pattern is commonly used in message queues. Of course, using the producer-consumer pattern in Java also requires attention to the thread-safety of buffers, which I won’t go into here.

A small example

Finally, a simple demo will be used to end this extended learning.

1. StoreQueue– buffer

public class StoreQueue<T> {

    private final BlockingQueue<T> queue = new LinkedBlockingQueue<>();

    /** * add data to queue **@paramData Data produced by the producer */
    public void add(T data) {
        try {
            queue.put(data);
        } catch(Exception e) { e.printStackTrace(); }}/** * get data from queue **@returnData obtained from the queue */
    public T get(a) {
        try {
            return queue.take();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null; }}Copy the code

In this example, we use the JDK’s own BlockingQueue to implement a buffer. We only need to implement the methods of putting and fetching data. If you want to implement a BlockingQueue, you need to be aware of the handling of blocking on the one hand, and on the other hand, you need to consider the thread safety issue.

2

public class Producer implements Runnable{

    private StoreQueue<String> storeQueue;

    public Producer(StoreQueue<String> storeQueue) {
        this.storeQueue = storeQueue;
    }

    @Override
    public void run(a) {
        for (int i = 0; i < 10; i++) {
            storeQueue.add(Thread.currentThread().getName() + ":"+ i); }}}Copy the code

3. Consumer

public class Consumer implements Runnable{

    private StoreQueue<String> storeQueue;

    public Consumer(StoreQueue<String> storeQueue) {
        this.storeQueue = storeQueue;
    }

    @Override
    public void run(a) {
        try {
            while (true) {
                String data = storeQueue.get();
                System.out.println("Current consuming thread:" + Thread.currentThread().getName() + ", received data:"+ data); }}catch(Exception e) { e.printStackTrace(); Thread.currentThread().interrupt(); }}}Copy the code

4. Execute the logic and result

Perform logical

		public static void main(String[] args) {
        StoreQueue<String> storeQueue = new StoreQueue<>();
        Producer producer = new Producer(storeQueue);
        Consumer consumer = new Consumer(storeQueue);
        Producer producerTwo = new Producer(storeQueue);
        Consumer consumerTwo = new Consumer(storeQueue);
        new Thread(producer).start();
        new Thread(consumer).start();
        new Thread(producerTwo).start();
        new Thread(consumerTwo).start();

    }
Copy the code

The results

Current consuming Thread: thread-1, received data: thread-0:0 Current consuming Thread: thread-1, received data: thread-0:1 Current consuming Thread: thread-1, received data: thread-0:0 Thread-0:2 Current consuming Thread: thread-1, received data: thread-0:3 Current consuming Thread: thread-1, received data: thread-0:4 Current consuming Thread: thread-3, received data: Thread-0:5 Current consuming Thread: thread-3, received data: thread-0:7 Current consuming Thread: thread-3, received data: thread-0:8 Current consuming Thread: thread-3, received data: Thread-0:9 Current consuming Thread: thread-3, received data: thread-2:0 Current consuming Thread: thread-3, received data: thread-2:1 Current consuming Thread: thread-3, received data: Thread - and current consumption threads, Thread, receives the data: Thread Thread - 2:3 current consumption: Thread - 3, receives the data: Thread Thread - then the current consumption: Thread - 3, to receive the data: Current consuming Thread: thread-3, received data: thread-2:6 Current consuming Thread: thread-3, received data: thread-2:7 Current consuming Thread: Thread-3, received data: Thread-2:8 Current consuming Thread: thread-3, received data: thread-2:9 Current consuming Thread: thread-1, received data: thread-0:6Copy the code

As you can see in the data results above, the data produced by different producers is consumed by only one consumer, and there is no thread-safety issue, thanks to the BlockingQueue used by the implementation buffer.