introduce
Blocking Queue means that when the queue is full, the queue blocks threads that insert elements until the queue is not satisfied; when the queue is empty, the queue blocks threads that acquire elements until the queue is not empty.A blocking queue is a container that producers use to store elements and consumers use to get them.
When a thread inserts/gets an action blocked by a full/empty queue, the queue also provides some mechanism to handle it, throw an exception, or return a special value, or the thread waits..
Method/Processing | throw | Return Special Values | Always Blocked | Timeout Exit |
---|---|---|---|---|
Insert Method | add(e) | offer(e) | put(e) | offer(e, timeout, unit) |
Remove Method | remove(o) | poll() | take() | poll(timeout, unit) |
Check Method | element() | peek() - Do not remove elements | Not available | Not available |
tips: if the queue is unbounded, the put method will never be blocked; the offer method always returns true.
Blocking queues in Java:
ArrayBlockingQueue
ArrayBlockingQueue is a bounded blocking queue implemented as an array.This queue sorts elements according to the first-in-first-out (FIFO) principle and does not guarantee fair access to threads by default.
Condition s achieve blocking by controlling concurrency with the reentrant exclusive lock ReentrantLock.
public class ArrayBlockingQueueTest { /** * 1. Since there is a bounded blocking queue, the initial size needs to be set * 2. Fairness can be set by default, which does not guarantee fair access to blocked threads */ private static ArrayBlockingQueue<String> QUEUE = new ArrayBlockingQueue<>(2, true); public static void main(String[] args) throws InterruptedException { Thread put = new Thread(() -> { // 3. Try inserting elements try { QUEUE.put("java"); QUEUE.put("javaScript"); // 4. Elements are full, blocking threads QUEUE.put("c++"); } catch (InterruptedException e) { e.printStackTrace(); } }); put.start(); Thread take = new Thread(() -> { try { // 5. Get an element System.out.println(QUEUE.take()); } catch (InterruptedException e) { e.printStackTrace(); } }); take.start(); // 6 javaScript,c++ System.out.println(QUEUE.take()); System.out.println(QUEUE.take()); } }
LinkedBlockingQueue
LinkedBlockingQueue is a bounded blocking queue implemented with a one-way chain table.The default and maximum length of this queue is Integer.MAX_VALUE.This queue sorts elements according to FIFO principle.
Like Array BlockingQueue, ReentrantLock controls concurrency, except that it uses two exclusive locks to control consumption and production. takeLock and putLock locks control production and consumption without interruption. As long as the queue is not full, the production line can always be produced; as long as the queue is not empty, consumptionThreads can be consumed all the time without blocking each other with exclusive locks.
tips: To avoid inaccurate concurrent calculations due to the use of double locks, an AtomicInteger variable is used to count the total number of elements.
LinkedBlockingDeque
LinkedBlockingDeque ue is a bounded blocking queue consisting of a two-way chain table structure that can insert and remove elements from both ends of the queue.It implements the BlockingDequeinterface, with many methods such as addFirst, addLast, offerFirst, offerLast, peekFirst, and peekLast, ending with the First word to insert, get, or remove the first element of a double-ended queue.A method that ends with the Last word to insert, get, or remove the last element of a double-ended queue.
LinkedBlockingDeque N ode implements a two-way queue by adding a variable prev to the previous Node.Similar to Array BlockingQueue in concurrency control, a single ReentrantLock is used to control concurrency.Because both ends of the queue can be consumed and produced, a shared lock is used.
A two-way blocking queue can be used in Work Stealing mode.
public class LinkedBlockingDequeTest { private static LinkedBlockingDeque<String> DEQUE = new LinkedBlockingDeque<>(2); public static void main(String[] args) { DEQUE.addFirst("java"); DEQUE.addFirst("c++"); // java System.out.println(DEQUE.peekLast()); // java System.out.println(DEQUE.pollLast()); DEQUE.addLast("php"); // c++ System.out.println(DEQUE.pollFirst()); } }
The tips:take() method calls takeFirst(), which should be used with caution.
PriorityBlockingQueue
PriorityBlockingQueue is an underlying unbounded blocking queue implemented by arrays with sorting capabilities.Because it is an unbound queue, the insert will never be blocked.By default, elements are in a natural ascending order.You can also customize the class implementation compareTo() method to specify the element collation, or specify the construction parameter Comparator to sort elements when you initialize PriorityBlockingQueue.
The bottom tier also uses ReentrantLock to control concurrency, and since only acquisition can block, only one Condition (only notification of consumption) is used.
public class PriorityBlockingQueueTest { private static PriorityBlockingQueue<String> QUEUE = new PriorityBlockingQueue<>(); public static void main(String[] args) { QUEUE.add("java"); QUEUE.add("javaScript"); QUEUE.add("c++"); QUEUE.add("python"); QUEUE.add("php"); Iterator<String> it = QUEUE.iterator(); while (it.hasNext()) { // c++ javaScript java python php // Same priority does not guarantee sorting order System.out.print(it.next() + " "); } } }
DelayQueue
DelayQueue is an unbounded blocking queue that supports delayed acquisition of elements.Queues are implemented using PriorityQueue.The elements in the queue must implement the Delayed interface. The elements are sorted by the delay priority, with the shorter delay being the first, and the elements can only be extracted from the queue at the end of the delay period.
Similar to PrirityBlockingQueue, the bottom level is an array, using a ReentrantLock to control concurrency.
Scenarios:
- The design of a caching system: DelayQueue can be used to save the validity period of cached elements, and a thread loop is used to query DelayQueue. Once an element is retrieved from DelayQueue, the validity period of the cache is reached.
- Timed Task Scheduling: Use DelayQueue to save the task and execution time that will be executed on that day, and start executing once the task is retrieved from DelayQueue, for example, TimerQueue is implemented using DelayQueue.
public class DelayElement implements Delayed, Runnable { private static final AtomicLong SEQUENCER = new AtomicLong(); /** * Identification element order */ private final long sequenceNumber; /** * Delay time, in nanoseconds */ private long time; public DelayElement(long time) { this.time = System.nanoTime() + time; this.sequenceNumber = SEQUENCER.getAndIncrement(); } @Override public long getDelay(TimeUnit unit) { return unit.convert(time - System.nanoTime(), NANOSECONDS); } @Override public int compareTo(Delayed other) { // compare zero if same object if (other == this) { return 0; } if (other instanceof DelayElement) { DelayElement x = (DelayElement) other; long diff = time - x.time; if (diff < 0) { return -1; } else if (diff > 0) { return 1; } else if (sequenceNumber < x.sequenceNumber) { return -1; } else { return 1; } } long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; } @Override public void run() { System.out.println("sequenceNumber" + sequenceNumber); } @Override public String toString() { return "DelayElement{" + "sequenceNumber=" + sequenceNumber + ", time=" + time + '}'; } }
public class DelayQueueTest { private static DelayQueue<DelayElement> QUEUE = new DelayQueue<>(); public static void main(String[] args) { // 1. Add 10 parameters for (int i = 1; i < 10; i++) { // Random delay in 2.5 seconds int nextInt = new Random().nextInt(5); long convert = TimeUnit.NANOSECONDS.convert(nextInt, TimeUnit.SECONDS); QUEUE.offer(new DelayElement(convert)); } // 3. Query Element Sorting - Short Delay Top Iterator<DelayElement> iterator = QUEUE.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } // 4. Element delay output is observed while (!QUEUE.isEmpty()) { Thread thread = new Thread(QUEUE.poll()); thread.start(); } } }
LinkedTransferQueue
LinkedTransferQueue is an unbounded blocking TransferQueue queue composed of a chain table structure.
A large number of CAS operations are used in concurrency control, and no locks are used.
LinkedTransferQueue has more tryTransfer and transfer methods than other blocked queues.
- Transfers: Transfers the element to a consumer, waiting if necessary to do so. Stored elements must wait until there is consumer consumption before returning.
- tryTransfer: Transfers the element to a waiting consumer immediately, if possible. If a consumer is waiting for a consumer element, pass in the element to the consumer.Otherwise, return to false immediately, without waiting for consumption.
SynchronousQueue
SynchronousQueue is a blocked queue that does not store elements.Each put operation must wait for a take operation, otherwise continuing the put operation will be blocked.
SynchronousQueue, by default, uses an unfair policy for thread access to the queue, does not use locks, and is all concurrent through CAS operations. It has a very high throughput and is better than LinkedBlockingQueue and Array BlockingQueue and is ideal for handling some highly efficient transitivity scenarios.Executors.newCachedThreadPool() uses SynchronousQueue for task delivery.
public class SynchronousQueueTest { private static class SynchronousQueueProducer implements Runnable { private BlockingQueue<String> blockingQueue; private SynchronousQueueProducer(BlockingQueue<String> queue) { this.blockingQueue = queue; } @Override public void run() { while (true) { try { String data = UUID.randomUUID().toString(); System.out.println(Thread.currentThread().getName() + " Put: " + data); blockingQueue.put(data); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } private static class SynchronousQueueConsumer implements Runnable { private BlockingQueue<String> blockingQueue; private SynchronousQueueConsumer(BlockingQueue<String> queue) { this.blockingQueue = queue; } @Override public void run() { while (true) { try { System.out.println(Thread.currentThread().getName() + " take(): " + blockingQueue.take()); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { final BlockingQueue<String> synchronousQueue = new SynchronousQueue<>(); SynchronousQueueProducer queueProducer = new SynchronousQueueProducer(synchronousQueue); new Thread(queueProducer, "producer - 1").start(); SynchronousQueueConsumer queueConsumer1 = new SynchronousQueueConsumer(synchronousQueue); new Thread(queueConsumer1, "consumer — 1").start(); SynchronousQueueConsumer queueConsumer2 = new SynchronousQueueConsumer(synchronousQueue); new Thread(queueConsumer2, "consumer — 2").start(); } }
- Reference Book: The Art of Java Concurrent Programming
- Reference Blog: https://www.cnblogs.com/konck/p/9473677.html