Concurrency understanding (blocking Queue)
I. How to design a blocking queue in java
About blocking queues in java
Queue is very suitable for business scenarios such as producers and consumers. For example, rabbitMq and activeMq are all encapsulated middleware for queue.
Java provides the implementation of blocking queues.
Queue is an interface that defines access to these basic interfaces:
Public interface Queue <E> extends Collection <E> {
// Put it in the queue. If not, an error is thrown. true is returned if the request is successful.
Boolean add (E e );
// Put it into the queue and return success or failure
Boolean offer (E e );
// Retrieve and remove the first element. If no element exists, an exception is thrown.
E remove ();
// Retrieve and remove the first element. If no element exists, null is returned.
E poll ();
// Retrieve the first element of the team, but do not remove the first element. If no element is removed, an exception is thrown.
E element ();
// Gets the first element of the team, but does not remove the first element. If no, null is returned.
E peek ();
}
What should I do when designing a blocking queue?
Blocking queues can be bounded or unbounded. Generally, unbounded queues are dangerous. In extreme cases, the JVM of the entire server is OutOfMemory due to the increasing memory.
Therefore, we will only consider how to design a bounded blocking queue.
There are two main points:
1. What should I do when the queue is full?
2. How can I improve the efficiency for a producer to put messages into a queue and consumers to retrieve messages from the queue?
If the queue is full, the following solutions are available:
The first solution is to block the producer and then let the consumer consume the message.
However, this will completely block external service threads (assuming that the message producer is a thread that provides external user services). The more user threads that are blocked, the more threads that the server cannot create, then all subsequent user requests will be blocked in the tcp connection queue. When the tcp connection queue is full, the entire server will stop responding.
This solution can be optimized by executing the task from the producer thread in the queue when the queue is full. This can reduce the consumption pressure.
The second solution is to serialize messages that cannot be placed in the backend to the disk. Of course, this solution sacrifices performance.
The third is to throw an exception.
The fourth type is that the return fails.
The java thread pool implements ThreadPoolExecutor. It has defined several common exceptions in which messages cannot be placed when the queue is full.
Such as AbortPolicy, DiscardPolicy, DiscardOldestPolicy, and CallerRunsPolicy.
In this example, if the queue is full and cannot contain messages, AbortPolicy directly rejects the message and throws an exception to the producer thread.
DiscardPolicy silently discards the newly submitted message. DiscardOldestPolicy removes the oldest message from the queue.
CallerRunsPolicy is the optimized version of the first solution mentioned above. The processing of message tasks that cannot be put into full queues will be executed by the producer call thread.
The code for setting a java thread pool to handle messages that are not fully loaded into a queue is as follows:
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (1, 1, 0,
TimeUnit. SECONDS, new LinkedBlockingQueue <Runnable> (1 ));
PoolExecutor. setRejectedExecutionHandler (new ThreadPoolExecutor. CallerRunsPolicy ());
PoolExecutor. setRejectedExecutionHandler (new ThreadPoolExecutor. AbortPolicy ());
PoolExecutor. setRejectedExecutionHandler (new ThreadPoolExecutor. DiscardPolicy ());
PoolExecutor. setRejectedExecutionHandler (new ThreadPoolExecutor. DiscardOldestPolicy ());
These solutions are basically used. In practice, it is best to improve the consumer's ability to retrieve and process messages, so as to avoid the situation where the queue is full.
The second problem is to improve the access and consumption of producers and consumers. The improvement is to reduce competition.
Of course, java provides SyncronziedQueue, which is applicable to multiple consumers of one producer.
In SyncronziedQueue, each time the producer puts a message to the queue, a consumer must retrieve the message from the queue before the producer can continue to put another message.
This means that the producer directly delivers the message to the consumer without a queue entity queue.
Executors's newCachedThreadPool uses SyncronziedQueue. When the producer puts messages at a speed greater than the consumer's message processing speed, if the number of threads is less than or equal to the maximum number of threads set by the thread pool, the thread pool tries to create a new consumption thread to process messages.
The test code is as follows:
Public class SynchronousQueueTest {
Public static void main (String [] args) throws Exception {
SynchronousQueue <Object> queue = new SynchronousQueue <Object> ();
For (int I = 0; I <5; I ++ ){
Thread t = new ConsumerThread (queue );
T. start ();
}
For (int I = 0; I <10; I ++ ){
// Queue. put (I );
Queue. offer (I );
}
}
Public static class ConsumerThread extends Thread {
Private SynchronousQueue <Object> queue;
ConsumerThread (SynchronousQueue <Object> queue ){
This. queue = queue;
}
@ Override
Public void run (){
Object item = null;
Try {
While (item = queue. take ())! = Null ){
System. out. println ("get:" + item. toString ()
+ "In thread :"
+ Thread. currentThread (). getId ());
}
} Catch (Exception e ){
//
}
System. out. println ("end ");
}
}
}
Ii. Dual-end queue
Java has a dual-end queue Deque, which features that it can be accessed at both the header and tail of the queue.
In this way, you can design a dual-end stealing application, that is, each consumption thread has its own queue, and then if the current consumption thread finishes processing its own queue message, it will retrieve data from other queue tails.
Here is a concept. Most java queue implementations store messages to the end of the team and take the first part of the team.
Java concurrent in practice: if each thread is retrieved from its own queue, this can reduce the competition for multiple consumption thread locks, which is understandable.
In fact, in our project, we will create a lot of queue (whether using rabbitmq or activemq), and then each queue will have only one consumption thread, such:
Create queue1 and queue2. Then, the consumption thread of queue1 is thread 1, and the consumption thread of queue2 is thread 2.
Of course, this concept is not stolen here. That is to say, when thread 1 consumes messages in queue1, the messages will not be retrieved from queue2.
Java concurrent in practice also said that when the current consumption thread completes the message consumption and obtains the message from the end of the queue owned by other consumption threads, this can reduce competition, which is confusing.
Assume that:
Thread 1 has queue 1, thread 2 has queue 2
Queue 2 has messages a and B, that is, a <-> B, a is at the head of the team, and B is at the end of the team.
It is required to retrieve the message from the beginning of the team and put the message from the end of the team
Assume that thread 1 needs to retrieve message B from queue 2 of thread 2 after processing the messages in queue 1.
At the same time, thread 2 Puts message c to queue 2.
At this time, a competition will occur. If no lock control is enabled, the following situations will occur:
Thread 1 performs the message B operation. It needs to point the next pointer of message a before message B in queue 2 to null.
But before thread 1 executes the next pointer of message a to the null statement, thread 2 successfully inserts message c into message B, that is, the next pointer of B points to the pointer.
After thread 2 completes, the message of queue2 is as follows, where <-> indicates a bidirectional pointer:
A <-> B <-> c
Thread 1 then points the next pointer of message B to null.
In this way, the message in queue2 becomes:
A <-> B (B points to null)
The operation of putting message c at the end of queue 2 is lost.