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;
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 Lock
and Condition
③ use of semaphores Semaphore
④ The JDK
own blocking queue
⑤ use flow duct
Use wait () and notify () to achieve
The premise is familiar with Object
several 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 queuequeue
;
② 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, usewhile(true){...}
way [production] or [out into the consumer] operations have been carried out.
④ However, due to the use of queuesynchronized
mode lock, the same time, either into or taken out, not both simultaneously.
Use the Lock and Condition implementation
The premise is familiar Lock
interfaces and common implementation classes ReentrantLock
, as well as Condition
two common methods:
-
await()
: Wait Condition is satisfied, the lock is released -
signal()
: Wake the others are waitingCondition
thread
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 embodiedLinkedBlockingQueue
.
Semaphore implemented using semaphores
The premise is familiar with the semaphore Semaphore
of use, especially release()
methods Semaphore
in release
does 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 , namelymaxSize
; each placed a commodity producers, the semaphore -1, i.e. executedacquire()
, indicates that the queue element has been added, to reduce a license; taken every consumer a product, the semaphore + 1, i.e., performingrelease()
, 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. executionrelease()
, represents an element added to queue; taken every consumer a product, the semaphore -1, i.e. executionacquire()
, 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 usedconcurrentWriteSemaphore
to achieve control of concurrent write queue, at the same time, carrying out an operation only on the queue: add or remove. If theconcurrentWriteSemaphore
semaphore 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 useLinkedList
is not allowed for such concurrent changes, otherwise it will take the case of spillage or empty appearance. So,concurrentWriteSemaphore
can only be set to 1, will result in performance with the use ofwait() / 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 BlockingQueue
not 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. ( ConconcurrentLinkedQueue
It is a high performance queue, but does not implement BlockingQueue
the 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
Author: maxwellyue
link: https: //www.jianshu.com/p/7cbb6b0bbabc
Source: Jane books