Related articles
Java Concurrent Programming (i) Thread definitions, states, and properties
Java concurrent Programming (ii) synchronization
Java Concurrency Programming (iii) volatile domains
Java Concurrency Programming (quad) Java memory model
Java Concurrency Programming (v) The realization principle and source code analysis of Concurrenthashmap
Preface
In the Android multithreaded (one) thread pool This article, when we want to create threadpoolexecutor need to pass in a parameter of type blockingqueue, it is blocking queue, in this article we will describe the block queue definition, kind, Implementation principles and applications.
1. What is a blocking queue
Blocking queues are often used for producer and consumer scenarios, where the producer is the thread that adds elements to the queue, and the consumer is the thread that takes the elements from the queue. The blocking queue is the container where the producer stores the elements, and the consumer only takes the elements from the container.
Blockingqueue has two common blocking scenarios
When there is no data in the queue, all threads on the consumer end will be automatically blocked (suspended) until there is data in the queue.
When the queue is filled with data, all threads on the producer side are automatically blocked (suspended) until the queue has empty positions and the thread is automatically awakened.
So the queue that supports both of these blocking scenarios is what we call a blocking queue.
the core approach of Blockingqueue
Put data:
- Offer (AnObject): If possible, add AnObject to Blockingqueue, that is, if blockingqueue can accommodate,
Returns TRUE, otherwise false is returned. (This method does not block the thread that is currently executing the method)
- Offer (E O, long timeout, timeunit unit), can set the waiting time, if within the specified time, not yet in the queue
Join Blockingqueue, the return fails.
- Put (anobject): Add AnObject to Blockingqueue, and if Blockqueue has no space, the thread calling this method is blocked until there is space in the blockingqueue to continue.
Get Data:
- Poll (time): Take the Blockingqueue in the first row of the object, if not immediately removed, you can wait for the timing of the parameters specified in
Returns null when not taken;
- Poll (long timeout, timeunit unit): An object that takes the first team from Blockingqueue, if within a specified time,
Once the queue has data, the data in the queue is returned immediately. Otherwise, the time-out is not well-timed and the return fails.
- Take (): Takes the Blockingqueue in the first place of the object, if the blockingqueue is empty, blocking into the waiting state until
Blockingqueue has new data to be added;
- Drainto (): Get all available data objects from Blockingqueue at once (you can also specify the number of data to be fetched).
This method can improve the efficiency of data acquisition, and does not need to lock or release multiple times in batches.
4 ways to handle insert and remove operations
Throw an exception: means that when the blocking queue is full, then inserting elements into the queue will throw IllegalStateException ("queue
Full ") exception. When the queue is empty, an Nosuchelementexception exception is thrown when an element is fetched from the queue.
Returns a special value that returns True if the Insert method returns success. Remove the method by taking an element from the queue and returning null if none
Always blocked: When the blocking queue is full, if the producer thread puts elements into the queue, the queue blocks the producer thread until it gets the data, or the response interrupts the exit. When the queue is empty, the consumer thread tries to take the element from the queue, and the queue blocks the consumer thread until the queue is available.
Timeout exit: When the blocking queue is full, the queue blocks the producer thread for a period of time, and if a certain amount of time is exceeded, the producer thread exits.
blocking queues in 2.Java
The JDK7 provides 7 blocking queues, namely:
- Arrayblockingqueue: Bounded blocking queue consisting of array structures.
- Linkedblockingqueue: Bounded blocking queues consisting of linked list structures.
- Priorityblockingqueue: A unbounded blocking queue that supports priority ordering.
- Delayqueue: The unbounded blocking queue implemented with the priority queue.
- Synchronousqueue: The blocking queue for the element is not stored.
- LinkedTransferQueue: Unbounded blocking queues consisting of linked list structures.
- Linkedblockingdeque: A two-way blocking queue consisting of a linked list structure.
Arrayblockingqueue
A bounded blocking queue implemented with an array. This queue sorts the elements according to the principle of first-out (FIFO). By default, the visitor is not guaranteed a fair access queue, the so-called Fair access queue refers to all the producer or consumer threads blocking, when the queue is available, you can access the queue in the order of blocking, that is, the first blocked producer thread, you can first insert elements into the queue, first blocking the consumer thread, You can get the elements from the queue first. In general, the throughput is reduced to ensure fairness. We can create a fair blocking queue using the following code:
new ArrayBlockingQueue(1000,true);
Linkedblockingqueue
A linked list-based blocking queue, similar to Arraylistblockingqueue, that queues the elements in FIFO (first-out), which also maintains a data buffer queue (which is made up of a list of lists), and when the producer puts a data into the queue, The queue fetches the data from the producer, caches it inside the queue, and the producer returns immediately; only if the queue buffer reaches the maximum cache capacity (Linkedblockingqueue can specify this value through the constructor), the producer queue is blocked until the consumer consumes a piece of data from the queue , the producer thread is awakened, and the consumer side of the process is based on the same principle. While Linkedblockingqueue is able to efficiently handle concurrency data, it also uses separate locks for both producer and consumer to control data synchronization, which means that producers and consumers can operate the data in the queue in parallel with high concurrency, This improves the concurrency performance of the entire queue.
As a developer, it is important to note that if you construct a Linkedblockingqueue object without specifying its capacity size, Linkedblockingqueue will default to a capacity (Integer.max_value) that is like an infinite size, In this case, if the producer's speed is greater than the consumer's speed, perhaps not until the queue is full of congestion, the system memory may have been exhausted.
Arrayblockingqueue and Linkedblockingqueue are the two most common and most commonly used blocking queues, and in general, in dealing with producer consumer issues between multiple threads, use these two classes enough.
Priorityblockingqueue
is an unbounded queue that supports priority. By default, elements are sorted in ascending order by nature. You can customize the implementation of the CompareTo () method to specify the ordering of elements, or when initializing Priorityblockingqueue, specify the construction parameters comparator to sort the elements. It is important to note that the order of the same priority elements cannot be guaranteed.
Delayqueue
is an unbounded blocking queue that supports delay-fetching elements. Queues are implemented using Priorityqueue. The elements in the queue must implement the delayed interface, and you can specify how long to get the current element from the queue when the element is created. Elements can be extracted from the queue only when the delay expires. We can apply delayqueue to the following scenarios:
- Cache system Design: You can use Delayqueue to save the cache element's validity period, using a thread to loop query Delayqueue, once the element can be obtained from delayqueue, it means that the cache is valid.
- Scheduled task scheduling: Use Delayqueue to save the task and execution time that will be performed on the day, and once the task is taken from Delayqueue, the Timerqueue is implemented using Delayqueue, for example.
Synchronousqueue
is a blocking queue that does not store elements. Each put operation must wait for a take operation, or the element cannot continue to be added. Synchronousqueue can be seen as a passer-by, responsible for passing the data of producer threads directly to the consumer thread. The queue itself does not store any elements, and is well suited for transitive scenarios, such as data that is used in one thread, passed to another thread, and synchronousqueue throughput is higher than linkedblockingqueue and Arrayblockingqueue.
LinkedTransferQueue
is an unbounded blocking Transferqueue queue made up of linked list structures. Compared to other blocking queues, LinkedTransferQueue has more trytransfer and transfer methods.
Transfer method. If there is currently a consumer waiting to receive elements (consumers use the Take () method or the poll () method with time limit), the transfer method can immediately transfer (transmit) to the consumer the element that the producer has passed in. If no consumer is waiting for an element to be received, the transfer method stores the element in the queue's tail node and waits until the element is consumed by the consumer before returning. The key code for the transfer method is as follows:
predpred, e, (how == TIMED), nanos);
The first line of code is trying to take the S node that holds the current element as the tail node. The second line of code is to let the CPU spin waiting for consumer spending elements. Because spin consumes the CPU, it spins a certain number of times and uses the Thread.yield () method to pause the currently executing thread and execute other threads.
Trytransfer method. is used to test whether the incoming elements of the producer can be passed directly to the consumer. Returns false if no consumer waits for the receiving element. The difference between the transfer method and the Trytransfer method is that the method returns immediately regardless of whether the consumer receives it or not. The transfer method is to wait until the consumer consumes the return.
For the Trytransfer (e E, long timeout, Timeunit unit) method with a time limit, an attempt is made to pass the producer's incoming element directly to the consumer, but if no consumer consumes the element, it waits for the specified time to return, and if the timeout has not consumed the element, Returns False if the element was consumed within the timeout period, which returns true.
Linkedblockingdeque
is a two-way blocking queue made up of linked list structures. The so-called bidirectional queue means that you can insert and remove elements from both ends of the queue. Double-ended queue because there is an operation queue of the entrance, in the multi-threaded simultaneously queued, also reduced half of the competition. Compared to other blocking queues, Linkedblockingdeque is more addfirst,addlast,offerfirst,offerlast,peekfirst,peeklast and so on, the method of ending with first word, means inserting, Gets (peek) or removes the first element of a double-ended queue. The method ending with the last word, which represents the insertion, gets or removes the final element of the double-ended queue. In addition, the Insert method add is equivalent to AddLast, and removing the method removes the Removefirst. But the Take method is equivalent to Takefirst, not knowing whether it is a JDK bug, or using a method with first and last suffixes to be clearer.
You can set the capacity to prevent the transition bloat when initializing the Linkedblockingdeque. In addition, the two-way blocking queue can be used in "work-stealing" mode.
3. How the blocking queue is implemented (JDK1.7)
Take Arrayblockingqueue as an example, let's look at the code first:
Public class arrayblockingqueue<e> extends abstractqueue<E >Implements Blockingqueue<e>, java.io.Serializable {PrivateStaticFinalLong Serialversionuid =-817911632652898426L/** The Queued items * * Finalobject[] items;/** Items Index for next take, poll, peek or remove * /int takeindex;/** Items index for next put, offer, or add * /int putindex;/** number of elements in the queue * /int count;FinalReentrantlock lock;/** Condition for waiting takes * / Private FinalCondition Notempty;/** Condition for waiting puts * / Private FinalCondition Notfull; ... Omitted
As can be seen from the above code Arrayblockingqueue is to maintain an array of type Object, Takeindex and Putindex, respectively, represent the first element of the team and the bottom of the tail element, count represents the number of elements in the queue, lock is a reentrant lock, Notempty and notfull are waiting conditions. Next we look at the key method put:
public void put (E E) throws interruptedexception {if (E = = null ) throw new NullPointerException (); Final Reentrantlock lock = this . lock ; lock . lockinterruptibly (); try {while (count = = items.length) notfull.await (); Enqueue (e); } finally {lock . Unlock (); } }
As can be seen from the implementation of the Put method, it acquires the lock first, and obtains the interruptible lock, then determines whether the current number of elements equals the length of the array, if equal, calls Notfull.await () to wait, and when awakened by another thread, inserts the element through the Enqueue (e) method. Finally unlocked.
/** * Inserts element at current put position, advances, and signals. * Call only when holding lock. */ privatevoidenqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; finalthis.items; items[putIndex] = x; if0; count++; notEmpty.signal(); }
After the insert succeeds, the thread that is waiting to fetch the element is awakened through Notempty. Take a look at the Take method:
publictake() throws InterruptedException { lockthis.lock; lock.lockInterruptibly(); try { while0) notEmpty.await(); return dequeue(); finally { lock.unlock(); } }
Similar to the Put method implementation, the Put method waits for the notfull signal, while the take method waits for the notempty signal. In the Take method, if an element can be taken, the element is obtained through the Dequeue method, and the following is the implementation of the Dequeue method:
private E dequeue () {//assert lock.getholdcount () = = 1; //assert items[takeindex]! = NULL; final object[] items = this . Items; @SuppressWarnings ( "unchecked" ) E x = (e) items[takeindex]; Items[takeindex] = null ; if (++takeindex = = items.length) Takeindex = 0 ; count--; if (Itrs! = null ) itrs.elementdequeued (); Notfull.signal (); return x; }
4. Blocking Queue usage Scenarios
In addition to the thread pool implementations using blocking queues, we can use the blocking queue in producer-consumer mode, first using object.wait (), object.notify (), and non-blocking queues to implement the producer-consumer model:
Public class Test { Private intQueuesize =Ten;Privatepriorityqueue<integer> queue =NewPriorityqueue<integer> (queuesize); Public Static voidMain (string[] args) {test test =NewTest (); Producer Producer = Test.NewProducer (); Consumer Consumer = Test.NewConsumer (); Producer.start (); Consumer.start (); } class Consumer extends Thread{@Override Public voidRun () { while(true) {synchronized (queue) { while(queue.size () = =0){Try{System.out.println ("Queue empty, waiting for data"); Queue.wait (); }Catch(Interruptedexception e) {E.printstacktrace (); Queue.notify (); }} queue.poll ();//Remove the first element of the team each timeQueue.notify (); } } } } class Producer extends Thread{@Override Public voidRun () { while(true) {synchronized (queue) { while(queue.size () = = queuesize) {Try{System.out.println ("The queue is full, waiting for free space"); Queue.wait (); }Catch(Interruptedexception e) {E.printstacktrace (); Queue.notify (); }} queue.offer (1);//Insert one element at a timeQueue.notify (); } } } } }
The following is the producer-consumer pattern implemented using a blocking queue:
Public class Test { Private intQueuesize =Ten;Privatearrayblockingqueue<integer> queue =NewArrayblockingqueue<integer> (queuesize); Public Static voidMain (string[] args) {test test =NewTest (); Producer Producer = Test.NewProducer (); Consumer Consumer = Test.NewConsumer (); Producer.start (); Consumer.start (); } class Consumer extends Thread{@Override Public voidRun () { while(true){Try{Queue.take (); }Catch(Interruptedexception e) {E.printstacktrace (); } } } } class Producer extends Thread{@Override Public voidRun () { while(true){Try{Queue.put (1); }Catch(Interruptedexception e) {E.printstacktrace (); } } } }}
It is clear that the use of blocking queues to implement a problem that does not require separate consideration of synchronization and inter-thread communication is simple to implement.
Resources:
The art of Java concurrent programming
Java Concurrent Programming (vi) blocking queues