DelayQueue is a * * * blocking queue that supports delay acquisition elements. And the elements in the queue must implement the Delayed interface. When you create an element, you can specify how long it takes to get the current element from the queue. Elements can only be obtained from the queue when the delay expires. DelayQueue has a wide range of applications. For example, it can be used to save the validity of elements in the cache, or it can be used to implement timing tasks.
Delayed interface
Before analyzing the DelayQueue source code, let's take a look at the Delayd interface. Its source code is defined as follows:
public interface Delayed extends Comparable < Delayed > { /** * Specifies the delay time for the returned object * @param unit [Time unit] * @return [Delay remaining, 0 or - 1 means the delay has expired] */ long getDelay(TimeUnit unit); }
We see that the Delayed interface inherits the Comparable interface, that is, the object implementing the Delayed interface must implement the getDelay(TimeUnit unit) method and the compareTo(T o) method. Here, compareTo(T o) method can be used to sort elements and put the delay time to the end of the queue.
DelayQueue constructor
The Delayed interface is analyzed above. Next, we analyze the constructor of DelayQueue. DelayQueue provides two kinds of constructors, one is a parameterless constructor, and the other is a constructor whose given set is a parameter. Its source code is as follows:
/** * Build an empty DelayQueue */ public DelayQueue() {} /** * Constructor with given set c as parameter * Put all elements in collection c into DelayQueue */ public DelayQueue(Collection < ? extends E > c) { this.addAll(c); }
addAll method is a method in AbstractQueue abstract class. Its source code is as follows:
public boolean addAll(Collection < ? extends E > c) { // Parameter detection if (c == null) throw new NullPointerException(); if (c == this) throw new IllegalArgumentException(); boolean modified = false; //Traversing elements in set c for (E e: c) // Call add method in DelayQueue if (add(e)) modified = true; return modified; }
From the above source code, we can see that the addAll method in the AbstractQueue abstract class is actually implemented by calling the add method in the DelayQueue class.
DelayQueue listing operation
DelayQueue provides 4 listing operations, respectively:
- add(E e): blocking adds the specified element to the delay queue. Because the queue is * * *, this method will never block.
- offer(E e): the blocking element is added to the delay queue, because the queue is * * * so this method will never block.
- Put (e e e): blocking adds the specified element to the delay queue, because the queue is * * * so this method will never block.
- Offer (e e e, long timeout, timeunit unit): for blocking, add the formulation element to the delay queue, because the queue is * * * so this method will never block.
You may wonder why the explanations of these listing methods are the same. This question will be answered later. Let's take a look at the source code definitions of these listing methods:
public boolean add(E e) { return offer(e); } public boolean offer(E e) { //Get reentrant lock final ReentrantLock lock = this.lock; //Lock up lock.lock(); try { //Call the offer method in PriorityQueue q.offer(e); //Call peek method in PriorityQueue if (q.peek() == e) { leader = null; available.signal(); } return true; } finally { //Release lock lock.unlock(); } } public void put(E e) { offer(e); } public boolean offer(E e, long timeout, TimeUnit unit) { return offer(e); }
Here we can see from the source code that the add(E e) method, put(E e) method and offer(E e,long timeout,TimeUnit unit) method are all implemented by calling the offer (E) method, which is why the explanations of these methods are the same. The core of the offer(E e) method is to call the offer (E) method in the PriorityQueue. The PriorityQueue and PriorityBlockingQueue are all binary queues, but the PriorityQueue is not blocked and the PriorityBlockingQueue is blocked.
DelayQueue dequeue operation
DelayQueue provides the operation methods of listing in 3. They are as follows:
- poll(): retrieve and delete the beginning of this queue, and return null if there is no delay element in this queue
- take(): retrieve and remove the headers of this queue, and if necessary wait until elements with expiration delays are available on the queue.
- poll(long timeout, TimeUnit unit): retrieve and delete the header of this queue. If necessary, wait until the element with expiration delay on the queue is available, or the specified waiting time expires.
Let's take a look at the original list operation.
poll():
The source code of poll operation is defined as follows:
public E poll() { //Get reentrant lock final ReentrantLock lock = this.lock; //Lock up lock.lock(); try { //Get the first element in the queue E first = q.peek(); //false if the element is null or the header element has not expired if (first == null || first.getDelay(NANOSECONDS) > 0) return null; else //Calling the dequeue method in PriorityQueue return q.poll(); } finally { lock.unlock(); } }
The only difference between this method and PriorityQueue's poll method is that there is more if (first = = null | first. Getdelay (nanoseconds) > 0) condition judgment, which means that if there are no elements in the queue or the elements in the queue are not expired, null will be returned.
take
take operation source code is defined as follows:
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; //Lock up lock.lockInterruptibly(); try { //Western cycle for (;;) { //View queue header elements E first = q.peek(); //If the queue header element is null, there is no data in the queue and the thread enters the waiting queue. if (first == null) available.await(); else { // Get the remaining delay time of the first element long delay = first.getDelay(NANOSECONDS); //If the remaining delay time < = 0 indicates that the element has expired, you can get the element from the queue if (delay <= 0) //Direct return to header element return q.poll(); //If the remaining delay time > 0 indicates that the element has not expired, set first to null to prevent memory overflow. first = null; // don't retain ref while waiting //If the leader is not null, enter the waiting queue directly and wait if (leader != null) available.await(); else { //If the leader is null, assign the current thread to the leader, and wait for delay for nanoseconds. Thread thisThread = Thread.currentThread(); leader = thisThread; try { available.awaitNanos(delay); } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) //Wake up thread available.signal(); lock.unlock(); } }
The take operation is slightly more complex than the poll operation, but the logic is relatively simple. Only check the remaining delay time of the element when getting the element. If the remaining delay time is less than or equal to 0, the queue header element will be returned directly. If the remaining delay time is > 0, judge whether the leader is null. If the leader is not null, it means that there is already a thread waiting to get the head element of the queue, so it directly enters the waiting queue to wait. If the leader is null, it means that this is the first thread to get the header element, assign the current thread to the leader, and then timeout waiting for the remaining delay time. One thing to note in the take operation is that first = null, because if first is not set to null, it will cause an exception of memory overflow. This is because each thread will hold a copy of first during concurrency, so first will not be released. If there are too many threads, it will lead to an exception of memory overflow.
poll(long timeout, TimeUnit unit)
The source code of timeout waiting to get the queue element is as follows:
public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { E first = q.peek(); if (first == null) { if (nanos <= 0) return null; else nanos = available.awaitNanos(nanos); } else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return q.poll(); if (nanos <= 0) return null; first = null; // don't retain ref while waiting if (nanos < delay || leader != null) nanos = available.awaitNanos(nanos); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { long timeLeft = available.awaitNanos(delay); nanos -= delay - timeLeft; } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } }
The logic of this dequeue operation is almost the same as that of take dequeue operation. The only difference is that take is waiting without time limit, while the change operation is waiting over time.
summary
The operation logic of DelayQueue is relatively simple, that is, when acquiring elements, judge whether the elements have expired. If they are expired, they can be acquired directly. If they are not expired, the poll operation returns null directly, and the take operation enters the waiting queue to wait.