The source code of this article is based on JDK13
DelayQueue delay queue
Official annotation translation
An unbounded blocking queue implementation for delay elements. Delay elements can only be obtained after their delay expires
The element of the queue header is the earliest expired element in the queue. If no element is expired, there will be no queue header element and the poll method will return a null
Expiration only works when the element's getDelay method returns a value less than or equal to 0
Although there are no expired elements, they cannot be obtained through take or poll. Other aspects are the same as normal elements
For example, size() returns the count of expired and unexpired elements, and the queue does not accept empty elements
This class and its Iterator implement all optional methods of the Collection and Iterator interfaces
This class is also part of the Java collection framework.
Source code
definition
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
First, it is a normal queue, and it is also a blocking queue. It has all their attributes. At the same time, the elements required to be put in implement the Delayed interface. The interface is defined as follows:
public interface Delayed extends Comparable<Delayed> { /** * Returns the remaining delay associated with this object, in the * given time unit. * * @param unit the time unit * @return the remaining delay; zero or negative values indicate * that the delay has already elapsed */ long getDelay(TimeUnit unit); }
Returns the remaining delay time according to the given time unit
attribute
// lock private final transient ReentrantLock lock = new ReentrantLock(); // Priority queue private final PriorityQueue<E> q = new PriorityQueue<E>(); // Thread waiting for queue header element private Thread leader; // Wait condition with element available private final Condition available = lock.newCondition();
The priority queue is used to save the element and record the thread waiting for the first element of the queue
This priority queue is in the java.util package. I won't explain it in detail. I'm sure everyone understands it
The wait condition available is provided to block threads and wake up
Construction method
public DelayQueue() {} public DelayQueue(Collection<? extends E> c) { this.addAll(c); }
Two construction methods are provided to construct an empty delay queue and a blocking queue loading a given set
Team series
public boolean add(E e) { return offer(e); } public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { // Insert element q.offer(e); // The queue header element is the element just inserted, indicating that there are available elements to wake up the waiting threads if (q.peek() == e) { leader = null; available.signal(); } return true; } finally { lock.unlock(); } } public void put(E e) { offer(e); } public boolean offer(E e, long timeout, TimeUnit unit) { return offer(e); }
In essence, the four methods of joining the queue Call offer. Directly call the offer of the internal priority queue and write without brain
It can be seen that this method always returns true. Because the delay queue is also unbounded, there is no need to block and no insertion failure
There are only two possibilities for insertion:
- success
- The memory burst and the program died
Out of team series
poll no element returned Null
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { // Get the first element E first = q.peek(); // If the first element is empty or the delay time of the first element does not expire, null is returned // Otherwise, the element is returned return (first == null || first.getDelay(NANOSECONDS) > 0) ? null : q.poll(); } finally { lock.unlock(); } }
First, check the first element. If it is not empty and has expired, it will pop up and return. Otherwise, it will return null
take blocking wait
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { // spin for (;;) { // View first element E first = q.peek(); // The first element is empty. Wait directly if (first == null) available.await(); else { // The first element has timed out and will pop up when it is available long delay = first.getDelay(NANOSECONDS); if (delay <= 0L) return q.poll(); // wait for first = null; // don't retain ref while waiting if (leader != null) available.await(); else { // If the current thread is the first one waiting for the first element of the queue, record the current thread and block only the remaining time, wake up and check whether it is available Thread thisThread = Thread.currentThread(); leader = thisThread; try { available.awaitNanos(delay); } finally { if (leader == thisThread) leader = null; } } } } } finally { // After getting the element, help wake up the waiting thread if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } }
The fetch element of this blocking version is a little more complex
- If the first element is empty, the current thread is blocked waiting
- If it is not empty and has expired, pop it up directly and return it. At this time, the element is obtained successfully
- It is not empty and has not expired. If the current thread is the first thread waiting for the queue head element, it will block the remaining delay time of the first element and wake up after expiration to check the status of the queue head element
- Instead of the first waiting thread, it blocks directly and waits for the first thread to wake up
- After obtaining the element successfully, if there are still available elements, help wake up the other waiting threads
poll(time,unit) timeout blocking version
It is very similar to the take code above, except that the time limit is added when each thread is blocked, so I won't repeat it
View series
size view number of elements
Why write this simple method? Note: the returned size is the total number of expired and unexpired
public int size() { final ReentrantLock lock = this.lock; lock.lock(); try { return q.size(); } finally { lock.unlock(); } }
Directly called the size() method of the internal priority queue without judging whether it expired
peek() view the first element of the queue without pop-up
In the delay queue, you always need to look at the first element of the queue. If it has expired, it will pop up. If it has not expired, it will not be processed. Therefore, take a brief look at the peek() method
public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return q.peek(); } finally { lock.unlock(); } }
Nothing, lock up, and then call the peek of the priority queue.
summary
Delay queue is essentially a blocking queue with priority, and the outgoing of the first element of the queue is limited according to the delay
- The experiment of priority queue uses java.util.PriorityQueue, which is essentially implemented as a heap
- The implementation of blocking queue uses the Condition condition. Since it is an unbounded queue, the incoming operation will not block. The outgoing behavior waits conditionally. When there are qualified elements, wake up all waiting threads
- The implementation of the delay attribute makes additional expiration judgment on the first element of the queue when leaving the queue. If it expires, it will pop up. If it does not expire, it will return null
- In terms of thread safety, since java.util.PriorityQueue is not thread safe, an additional ReentrantLock is used to restrict read and write access to data
Reference articles
End.
Contact me
Finally, welcome to my personal official account, Yan Yan ten, which will update many learning notes from the backend engineers. I also welcome direct official account or personal mail or email to contact me.
The above are all personal thoughts. If there are any mistakes, please correct them in the comment area.
Welcome to reprint, please sign and keep the original link.
Contact email: huyanshi2580@gmail.com
For more study notes, see personal blog or WeChat official account, Yan Yan ten > > Huyan ten