1. Queue
A queue is a special linear table, which is special in that it only allows deletion operations at the front end of the table (front), and insertion operations at the back end (rear) of the table. Like a stack, a queue is a linear table with limited operations. The end of the insertion operation is called the tail of the queue, and the end of the deletion operation is called the head.
Inserting a queue element into the queue is called enqueue, and removing a queue element from the queue is called dequeue. Because the queue can only be inserted at one end and deleted at the other end, only the elements that enter the queue at the earliest can be deleted from the queue first, so the queue is also called FIFO (first in first out) linear table
2. Blocking queue
A blocking queue (BlockingQueue) is a queue that supports two additional operations. The two additional operations are:
When the queue is empty, the thread that gets the element blocks waiting for the queue to become non-empty
When the queue is full, the thread storing the element blocks waiting for the queue to become available
Blocking queues are often used in the scenario of producers and consumers. The producer is the thread that adds elements to the queue, and the consumer is the thread that takes elements from the queue. The blocking queue is the container where the producer stores elements, and the consumer only takes elements from the container
BlockingQueue interface inheritance diagram
BlockingQueue interface implementation class
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class BlockQueueTest {
public static void main(String[] args) {
BlockingQueue blockingQueue = new SynchronousQueue();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + " put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + " put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take " + blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take " + blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take " + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
output
T1 put 1
T2 take 1
T1 put 2
T2 take 2
T1 put 3
T2 take 3
(1) Blocking queue BlockingQueue four processing methods
method/handling method | Throw an exception | return special value | keep blocking | timeout exit |
insert method | add(E) | offer(E) | put(E) | offer(E, long, TimeUnit) |
removal method | remove() | poll() | take() | poll(long, TimeUnit) |
Inspection Method | element() | peek() | unavailable | unavailable |
The two methods that really reflect blocking are put(E) / take()
-
Throw an exception
When the queue is full, if you insert an element into the queue, a defaultIllegalStateException
exception .
When the queue is empty, if you get an element from the queue again,NoSuchElementException
an exception -
Return a special value
Every time an insert operation is performed on the queue, it will return whether the element is inserted successfully. If it is successful, it will return.true
If
it is a get operation, it will get an element from the queue. If there is no such element, the return value will benull
. -
Always block
When the queue is full, if the producer thread continues toput
add elements , the queue will block the producer thread until the queue is available or the response interrupt exits.
When the queuenull
is , if the consumer threadtake
adds elements from the queue, The queue will block the consumer thread until the queue is no longernull
. -
Timeout exit
When the blocking queue is full, if the producer thread continues to insert elements into the queue, the queue will block the producer thread for a period of time, and if the specified time is exceeded, the producer thread will exit
1. Throw an exception
When the queue is full, if you insert elements into the queue, a default IllegalStateException
exception .
When the queue is empty, if you get elements from the queue again, NoSuchElementException
an exception
/**
* 1、抛出异常
* 当队列满的时候, 如果再向队列中插入元素, 会抛出默认的 IllegalStateException 异常.
* 当队列为空时候, 如果再从队列中获取元素, 会抛出 NoSuchElementException 异常
*/
public static void throwException(){
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// java.lang.IllegalStateException: Queue full
//System.out.println(blockingQueue.add("d"));
// 获取队列首元素
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// java.util.NoSuchElementException
//System.out.println(blockingQueue.remove());
}
true
true
true
a
a
b
c
add() method
public abstract class AbstractQueue<E>
extends AbstractCollection<E>
implements Queue<E> {
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
}
remove() method
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
2. Return a special value
Every time an insert operation is performed on the queue, it will return whether the element is inserted successfully, and if it is successful, it will return. true
If
it is an acquisition operation, it will obtain an element from the queue. If there is no such element, the return value will benull
/**
* 2、返回特殊值
* 每次对队列执行插入操作, 会返回元素是否插入成功, 成功则返回 true.
* 如果是获取操作, 则是从队列中获取一个元素, 没有这个元素的话, 返回值为 null
*/
public static void returnSpecial(){
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// 队列满了,返回false;不抛出异常
System.out.println(blockingQueue.offer("d"));
// 获取队列首元素
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// 队列为空,返回null;不抛出异常
System.out.println(blockingQueue.poll());
}
The implementation of throwing an exception and returning a special value method is the same, but the handling of failed operations is different
Through the source code of AbstractQueue, it can be found that add(e), remove(), element() are all implemented based on offer(), poll() and peek() respectively
public boolean add(E arg0) {
if (this.offer(arg0)) {
return true;
} else {
throw new IllegalStateException("Queue full");
}
}
public E remove() {
Object arg0 = this.poll();
if (arg0 != null) {
return arg0;
} else {
throw new NoSuchElementException();
}
}
public E element() {
Object arg0 = this.peek();
if (arg0 != null) {
return arg0;
} else {
throw new NoSuchElementException();
}
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
// 如果原来队列为空,重置leader线程,通知available条件
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
//因为DelayQueue不限制长度,因此添加元素的时候不会因为队列已满产生阻塞,因此带有超时的offer方法的超时设置是不起作用的
public boolean offer(E e, long timeout, TimeUnit unit) {
// 和不带timeout的offer方法一样
return offer(e);
}
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek();
if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0)
return null;
else
return q.poll();
} finally {
lock.unlock();
}
}
3. Always blocked
When the queue is full, if the producer thread continues to add put
elements , the queue will block the producer thread until the queue is available or the response interrupt exits
When the queue null
is , if the consumer thread adds take
elements to the queue, the queue will block Hold the consumer thread until the queue is no longernull
/**
* 3、一直阻塞
* 当队列满的时候, 如果生产者线程继续向队列中 put 元素, 队列将会一直阻塞生产者线程, 直到队列可用或者响应中断退出.
* 当队列为 null 的时候, 如果消费者线程从队列中 take 元素, 队列会阻塞住消费者线程, 直到队列不为 null
*/
public static void alwaysBlock() throws InterruptedException {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// 程序一直等待
blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
// 程序一直等待
System.out.println(blockingQueue.take());
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
4. Timeout exit
When the blocking queue is full, if the producer thread continues to insert elements into the queue, the queue will block the producer thread for a period of time. If the specified time is exceeded, the producer thread will exit
/**
* 4、超时退出
* 当阻塞队列满时, 如果生产者线程继续向队列中插入元素, 队列会阻塞生产者线程一段时间
* 如果超过了这个指定的时间, 生产者线程就会退出
*/
public static void overTimeQuit() throws InterruptedException {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// 队列满了,超时2S退出
blockingQueue.offer("d", 2,TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// 队列为空,超时2S退出
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
(2) BlockingQueue core method
public interface BlockingQueue<E> extends Queue<E> {
//将给定元素设置到队列中,如果设置成功返回true, 否则抛出异常。如果是往限定了长度的队列中设置值,推荐使用offer()方法。
boolean add(E e);
//将给定的元素设置到队列中,如果设置成功返回true, 否则返回false. e的值不能为空,否则抛出空指针异常。
boolean offer(E e);
//将元素设置到队列中,如果队列中没有多余的空间,该方法会一直阻塞,直到队列中有多余的空间。
void put(E e) throws InterruptedException;
//将给定元素在给定的时间内设置到队列中,如果设置成功返回true, 否则返回false.
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
//从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。
E take() throws InterruptedException;
//在给定的时间里,从队列中获取值,如果没有取到会抛出异常。
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
//获取队列中剩余的空间。
int remainingCapacity();
//从队列中移除指定的值。
boolean remove(Object o);
//判断队列中是否拥有该值。
public boolean contains(Object o);
//将队列中值,全部移除,并发设置到给定的集合中。
int drainTo(Collection<? super E> c);
//指定最多数量限制将队列中值,全部移除,并发设置到给定的集合中。
int drainTo(Collection<? super E> c, int maxElements);
}
(3) Seven blocking queues in Java
ArrayBlockingQueue: a bounded blocking queue composed of an array structure
LinkedBlockingQueue: a bounded blocking queue composed of a linked list structure
PriorityBlockingQueue: an unbounded blocking queue that supports priority sorting DelayQueue: an unbounded blocking queue
implemented using a priority queue
SynchronousQueue: a A blocking queue that does not store elements
LinkedTransferQueue: an unbounded blocking queue composed of a linked list structure
LinkedBlockingDeque: a bidirectional blocking queue composed of a linked list structure
1、ArrayBlockingQueue
ArrayBlockingQueue is a bounded blocking queue implemented with an array. This queue orders elements on a first-in-first-out (FIFO) basis. By default, visitors are not guaranteed to access the queue fairly. The so-called fair access queue refers to all blocked producer threads or consumer threads. When the queue is available, the queue can be accessed in the order of blocking, that is, the blocked producer thread first , you can insert elements into the queue first, and the consumer thread that blocks first can get elements from the queue first. Usually, throughput is reduced in order to ensure fairness . We can create a fair blocking queue with the following code:
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
The fairness of its access is achieved through the ReentrantLock lock.
2、linkedBlockingQueue
linkedBlockingQueue is a bounded blocking queue implemented with a linked list. The default and maximum length of this queue is Integer.MAX_VALUE. This queue sorts the elements on a first-in, first-out basis.
3、PriorityBlockingQueue
PriorityBlockingQueue is an unbounded queue that supports priority. By default, the elements are arranged in natural order, and the sorting rules of the elements can also be specified through the comparator. Elements are sorted in ascending order.
4、DelayQueue
DelayQueue is an unbounded blocking queue that supports delayed retrieval of elements. The queue is implemented using PriorityQueue. The elements in the queue must implement the Delayed interface. When creating an element, you can specify how long it takes to get the current element from the queue. Elements are only taken from the queue when the delay expires. We can use DelayQueue in the following application scenarios:
The design of the cache system: DelayQueue can be used to save the validity period of the cached elements, and a thread can be used to query the DelayQueue in a loop. Once the elements can be obtained from the DelayQueue, it means that the cache validity period has arrived.
Timing task scheduling. Use DelayQueue to save the tasks and execution time that will be executed that day. Once the tasks are obtained from DelayQueue, they will be executed. For example, TimerQueue is implemented using DelayQueue.
How to implement the Delayed interface
We can refer to the ScheduledFutureTask class in ScheduledThreadPoolExecutor. This class implements the Delayed interface. First of all: when the object is created, use time to record when the object can be used before, the code is as follows:
1 2 3 4 5 6 |
|
Then use getDelay to query how long the current element needs to be delayed. The code is as follows:
1 2 3 |
|
It can be seen from the constructor that the unit of the delay time parameter ns is nanoseconds. It is best to use nanoseconds when designing your own, because you can specify any unit when getDelay is used. Once nanoseconds are used as the unit, the delay time is less than accurate. Nanoseconds are troublesome. Please note that when the time is less than the current time, getDelay will return a negative number.
Finally, we can use time to specify its order in the queue, for example: put the one with the longest delay time at the end of the queue.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
How to implement delayed blocking queue
The implementation of the delay blocking queue is very simple. When the consumer gets the element from the queue, if the element does not reach the delay time, the current thread will be blocked.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
5、SynchronousQueue
SynchronousQueue is a blocking queue that does not store elements. Each put operation must wait for a take operation, otherwise elements cannot be added. SynchronousQueue can be regarded as a passer, responsible for directly passing the data processed by the producer thread to the consumer thread. The queue itself does not store any elements, which is very suitable for transitive scenarios, such as data used in one thread, passed to another thread for use, and the throughput of SynchronousQueue is higher than that of
linkedBlockingQueue 和 ArrayBlockingQueue。
It supports fair access queues. The default policy mechanism is still unfair
6、linkedTransferQueue
linkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列linkedTransferQueue多了tryTransfer和transfer方法。
transfer方法
如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。
tryTransfer方法
是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。
对于带有时间限制的tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回false,如果在超时时间内消费了元素,则返回true。
7、linkedBlockingDeque
linkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其他的阻塞队列,linkedBlockingDeque多了addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast等方法,以First单词结尾的方法,表示插入,获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。另外插入方法add等同于addLast,移除方法remove等效于removeFirst。但是take方法却等同于takeFirst,不知道是不是Jdk的bug,使用时还是用带有First和Last后缀的方法更清楚。在初始化linkedBlockingDeque时可以初始化队列的容量,用来防止其再扩容时过渡膨胀。另外双向阻塞队列可以运用在“工作窃取”模式中