Producer consumer model implemented in Java

Producer consumer model

Producer consumer model can be described as:
① producers continue to produce until the warehouse filled with goods, stop the production into a wait state; to continue production after the warehouse dissatisfaction;
② consumers continue to consume until the warehouse empty, then stop spending into a wait state ; the warehouse is not empty, continue to spend;
③ can have multiple producers, consumers may also have more;

Producer consumer model

Corresponding to the program buffer corresponding to the warehouse, to be used as a buffer queue, and the queue should be bounded, i.e., the maximum capacity is fixed; enters a waiting state, then the current thread to be blocked, until a certain condition is met , and then wake up.

Common implementations are the following.
Use ① wait()and notify()
② use Lockand Condition
③ use of semaphores Semaphore
④ The JDKown blocking queue
⑤ use flow duct


Use wait () and notify () to achieve

The premise is familiar with Objectseveral methods:

  • wait(): The current thread releases the lock, wait until the notice, go get a lock
  • sleep(): The current thread to sleep, but do not release the lock
  • notify(): Wake up other threads are wait

Reference code is as follows:

public class ProducerConsumer1 {

    class Producer extends Thread { private String threadName; private Queue<Goods> queue; private int maxSize; public Producer(String threadName, Queue<Goods> queue, int maxSize) { this.threadName = threadName; this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { //模拟生产过程中的耗时操作 Goods goods = new Goods(); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (queue) { while (queue.size() == maxSize) { try { System.out.println("队列已满,【" + threadName + "】进入等待状态"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.add(goods); System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size()); queue.notifyAll(); } } } } class Consumer extends Thread { private String threadName; private Queue<Goods> queue; public Consumer(String threadName, Queue<Goods> queue) { this.threadName = threadName; this.queue = queue; } @Override public void run() { while (true) { Goods goods; synchronized (queue) { while (queue.isEmpty()) { try { System.out.println("队列已空,【" + threadName + "】进入等待状态"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } goods = queue.remove(); System.out.println("【" + threadName + "】消费了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size()); queue.notifyAll(); } //模拟消费过程中的耗时操作 try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Test public void test() { int maxSize = 5; Queue<Goods> queue = new LinkedList<>(); Thread producer1 = new Producer("生产者1", queue, maxSize); Thread producer2 = new Producer("生产者2", queue, maxSize); Thread producer3 = new Producer("生产者3", queue, maxSize); Thread consumer1 = new Consumer("消费者1", queue); Thread consumer2 = new Consumer("消费者2", queue); producer1.start(); producer2.start(); producer3.start(); consumer1.start(); consumer2.start(); while (true) { } } } 

Some caveats:

① determine the lock object is a queue queue;

② Do not put production and consumption in the process of writing a synchronized block, these operations do not need to sync, sync is only taken in and out of these two actions;

③ because it is sustained production, sustainable consumption, use while(true){...}way [production] or [out into the consumer] operations have been carried out.

④ However, due to the use of queue synchronizedmode lock, the same time, either into or taken out, not both simultaneously.


Use the Lock and Condition implementation

The premise is familiar Lockinterfaces and common implementation classes ReentrantLock, as well as Conditiontwo common methods:

  • await(): Wait Condition is satisfied, the lock is released
  • signal(): Wake the others are waiting Conditionthread
    reference code is as follows:
public class ProducerConsumer2 {

    class Producer extends Thread { private String threadName; private Queue<Goods> queue; private Lock lock; private Condition notFullCondition; private Condition notEmptyCondition; private int maxSize; public Producer(String threadName, Queue<Goods> queue, Lock lock, Condition notFullCondition, Condition notEmptyCondition, int maxSize) { this.threadName = threadName; this.queue = queue; this.lock = lock; this.notFullCondition = notFullCondition; this.notEmptyCondition = notEmptyCondition; this.maxSize = maxSize; } @Override public void run() { while (true) { //模拟生产过程中的耗时操作 Goods goods = new Goods(); try { Thread.sleep(new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); try { while (queue.size() == maxSize) { try { System.out.println("队列已满,【" + threadName + "】进入等待状态"); notFullCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } queue.add(goods); System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size()); notEmptyCondition.signalAll(); } finally { lock.unlock(); } } } } class Consumer extends Thread { private String threadName; private Queue<Goods> queue; private Lock lock; private Condition notFullCondition; private Condition notEmptyCondition; public Consumer(String threadName, Queue<Goods> queue, Lock lock, Condition notFullCondition, Condition notEmptyCondition) { this.threadName = threadName; this.queue = queue; this.lock = lock; this.notFullCondition = notFullCondition; this.notEmptyCondition = notEmptyCondition; } @Override public void run() { while (true) { Goods goods; lock.lock(); try { while (queue.isEmpty()) { try { System.out.println("队列已空,【" + threadName + "】进入等待状态"); notEmptyCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } goods = queue.remove(); System.out.println("【" + threadName + "】消费了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size()); notFullCondition.signalAll(); } finally { lock.unlock(); } //模拟消费过程中的耗时操作 try { Thread.sleep(new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Test public void test() { int maxSize = 5; Queue<Goods> queue = new LinkedList<>(); Lock lock = new ReentrantLock(); Condition notEmptyCondition = lock.newCondition(); Condition notFullCondition = lock.newCondition(); Thread producer1 = new ProducerConsumer2.Producer("生产者1", queue, lock, notFullCondition, notEmptyCondition, maxSize); Thread producer2 = new ProducerConsumer2.Producer("生产者2", queue, lock, notFullCondition, notEmptyCondition, maxSize); Thread producer3 = new ProducerConsumer2.Producer("生产者3", queue, lock, notFullCondition, notEmptyCondition, maxSize); Thread consumer1 = new ProducerConsumer2.Consumer("消费者1", queue, lock, notFullCondition, notEmptyCondition); Thread consumer2 = new ProducerConsumer2.Consumer("消费者2", queue, lock, notFullCondition, notEmptyCondition); Thread consumer3 = new ProducerConsumer2.Consumer("消费者3", queue, lock, notFullCondition, notEmptyCondition); producer1.start(); producer2.start(); producer3.start(); consumer1.start(); consumer2.start(); consumer3.start(); while (true) { } } } 

Must pay attention to:

loading and unloading operations are the same with a lock, so at the same time, either into or taken out, not both at the same time. Thus, with the use of wait () and notify () to achieve a similar, way to achieve this does not maximize the use of the buffer (i.e., queue in the example). To achieve the same time, may be placed and can be removed, will have to use two reentrant lock, control the loading and unloading operation, reference may be embodied LinkedBlockingQueue.


Semaphore implemented using semaphores

The premise is familiar with the semaphore Semaphoreof use, especially release()methods Semaphorein releasedoes not have to be sure before acquire. (If you are not familiar with Semaphore, you can refer to read [concurrent] multi-threading and Java Concurrency Tools )

There is no requirement that a thread that releases a permit must
have acquired that permit by calling acquire.
Correct usage of a semaphore is established by programming convention
in the application.

Reference code is as follows:

public class ProducerConsumer4 {


    class Producer extends Thread { private String threadName; private Queue<Goods> queue; private Semaphore queueSizeSemaphore; private Semaphore concurrentWriteSemaphore; private Semaphore notEmptySemaphore; public Producer(String threadName, Queue<Goods> queue, Semaphore concurrentWriteSemaphore, Semaphore queueSizeSemaphore, Semaphore notEmptySemaphore) { this.threadName = threadName; this.queue = queue; this.concurrentWriteSemaphore = concurrentWriteSemaphore; this.queueSizeSemaphore = queueSizeSemaphore; this.notEmptySemaphore = notEmptySemaphore; } @Override public void run() { while (true) { //模拟生产过程中的耗时操作 Goods goods = new Goods(); try { Thread.sleep(new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } try { queueSizeSemaphore.acquire();//获取队列未满的信号量 concurrentWriteSemaphore.acquire();//获取读写的信号量 queue.add(goods); System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size()); } catch (InterruptedException e) { e.printStackTrace(); }finally { concurrentWriteSemaphore.release(); notEmptySemaphore.release(); } } } } class Consumer extends Thread { private String threadName; private Queue<Goods> queue; private Semaphore queueSizeSemaphore; private Semaphore concurrentWriteSemaphore; private Semaphore notEmptySemaphore; public Consumer(String threadName, Queue<Goods> queue, Semaphore concurrentWriteSemaphore, Semaphore queueSizeSemaphore, Semaphore notEmptySemaphore) { this.threadName = threadName; this.queue = queue; this.concurrentWriteSemaphore = concurrentWriteSemaphore; this.queueSizeSemaphore = queueSizeSemaphore; this.notEmptySemaphore = notEmptySemaphore; } @Override public void run() { while (true) { Goods goods; try { notEmptySemaphore.acquire(); concurrentWriteSemaphore.acquire(); goods = queue.remove(); System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size()); } catch (InterruptedException e) { e.printStackTrace(); }finally { concurrentWriteSemaphore.release(); queueSizeSemaphore.release(); } //模拟消费过程中的耗时操作 try { Thread.sleep(new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Test public void test() { int maxSize = 5; Queue<Goods> queue = new LinkedList<>(); Semaphore concurrentWriteSemaphore = new Semaphore(1); Semaphore notEmptySemaphore = new Semaphore(0); Semaphore queueSizeSemaphore = new Semaphore(maxSize); Thread producer1 = new ProducerConsumer4.Producer("生产者1", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore); Thread producer2 = new ProducerConsumer4.Producer("生产者2", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore); Thread producer3 = new ProducerConsumer4.Producer("生产者3", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore); Thread consumer1 = new ProducerConsumer4.Consumer("消费者1", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore); Thread consumer2 = new ProducerConsumer4.Consumer("消费者2", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore); Thread consumer3 = new ProducerConsumer4.Consumer("消费者3", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore); producer1.start(); producer2.start(); producer3.start(); consumer1.start(); consumer2.start(); consumer3.start(); while (true) { } } } 

Must pay attention to:

① understand three semaphore code meaning
queueSizeSemaphore :( number of licenses which can be understood as the number of elements in the queue can then put in), the initial number of licenses for the semaphore to warehouse size , namely maxSize; each placed a commodity producers, the semaphore -1, i.e. executed acquire(), indicates that the queue element has been added, to reduce a license; taken every consumer a product, the semaphore + 1, i.e., performing release(), indicates that the queue has one less element, give you a license.
notEmptySemaphore :( wherein the number of licenses can be understood as the number of elements in the queue can be removed), the initial number of licenses of the semaphore is 0; each placed a commodity producers, the semaphore +1, i.e. execution release(), represents an element added to queue; taken every consumer a product, the semaphore -1, i.e. execution acquire(), represents a queue element has been reduced, to reduce a license;
concurrentWriteSemaphore , equivalent to a write lock, or placed in remove the merchandise, they both need to obtain and release licenses.

② Since the implementation used concurrentWriteSemaphoreto achieve control of concurrent write queue, at the same time, carrying out an operation only on the queue: add or remove. If the concurrentWriteSemaphoresemaphore is initialized to a value of 2 or more than 2, there will be multiple producers or simultaneously into multiple consumers simultaneously with consumption, and the use LinkedListis not allowed for such concurrent changes, otherwise it will take the case of spillage or empty appearance. So, concurrentWriteSemaphorecan only be set to 1, will result in performance with the use of wait() / notify()a similar way, performance is not high.


Jdk that comes with the use of blocking queue implementation

The premise is to take place to keep in mind two blocking method, because blocking queue provides many ways to access elements of measures in several ways to access the queue is full / empty when taken are as follows:

The method / process mode Throw an exception Returns the special value Has been blocked Timeout exit
insert add(e) offer(e) put(e) offer(e, time, unit)
Remove remove() poll() take() poll(time, unit)
an examination element() peek() unavailable unavailable

So, here, to choose put()and take()these two methods will be blocked.

Reference code is as follows:

public class ProducerConsumer3 {

    class Producer extends Thread { private String threadName; private BlockingQueue<Goods> queue; public Producer(String threadName, BlockingQueue<Goods> queue) { this.threadName = threadName; this.queue = queue; } @Override public void run() { while (true){ Goods goods = new Goods(); try { //模拟生产过程中的耗时操作 Thread.sleep(new Random().nextInt(100)); queue.put(goods); System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread { private String threadName; private BlockingQueue<Goods> queue; public Consumer(String threadName, BlockingQueue<Goods> queue) { this.threadName = threadName; this.queue = queue; } @Override public void run() { while (true){ try { Goods goods = queue.take(); System.out.println("【" + threadName + "】消费了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size()); //模拟消费过程中的耗时操作 Thread.sleep(new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Test public void test() { int maxSize = 5; BlockingQueue<Goods> queue = new LinkedBlockingQueue<>(maxSize); Thread producer1 = new ProducerConsumer3.Producer("生产者1", queue); Thread producer2 = new ProducerConsumer3.Producer("生产者2", queue); Thread producer3 = new ProducerConsumer3.Producer("生产者3", queue); Thread consumer1 = new ProducerConsumer3.Consumer("消费者1", queue); Thread consumer2 = new ProducerConsumer3.Consumer("消费者2", queue); producer1.start(); producer2.start(); producer3.start(); consumer1.start(); consumer2.start(); while (true) { } } } 

Must pay attention to:

If LinkedBlockingQueue implemented as queues, can be achieved: at the same time, may be placed and can be removed, because the two internal LinkedBlockingQueue reentrant lock, control the loaded and unloaded.
If ArrayBlockingQueue implemented as queues, at the same time you can add or remove, because the internal ArrayBlockingQueue uses only a reentrant locks to control concurrency modify operation.


Use the pipeline flow to achieve

//ALL


Caching framework lock-free: Disruptor

BlockingQueue achieve producer and consumer patterns easy to understand, but BlockingQueuenot a high-performance implementation: It is completely blocked and the use of locks to achieve synchronization between threads. In the case of high concurrency, and its performance is not particularly advantageous. ( ConconcurrentLinkedQueueIt is a high performance queue, but does not implement BlockingQueuethe interface, i.e., blocking operation is not supported).

LMAX Disruptor is developed efficient lock-free buffer queue. It uses lock-free way to implement a circular queue, well adapted to carry producer and consumer patterns, such as: publishing events and messages.

// TODO code implementation scenarios


reference

Java implementation of the producer - consumer model : the performance of various implementations of
high-performance producers - consumers: the lock-free : no lock to achieve
five kinds of implementations of Java producer and consumer model of
producer / consumer problem the multiple Java implementations of
Java blocking queue ArrayBlockingQueue LinkedBlockingQueue and realization of the principle analysis : the difference between two common blocking queue

The complete code sample



Author: maxwellyue
link: https: //www.jianshu.com/p/7cbb6b0bbabc
Source: Jane books

Guess you like

Origin www.cnblogs.com/xiaoshen666/p/11258643.html