DelayQueue of concurrent queues

Four concurrent queues have been mentioned. DelayQueue is the last one. This is an unbounded blocking delay queue. The bottom layer is based on the PriorityBlockingQueue mentioned earlier. Each element in the queue has an expiration time. When getting elements from the queue, only the expired elements will come out of the queue, and the elements in the queue head are the fastest expired elements;

 

1, Easy to use

We can see that we can set our own timeout and priority queue comparison rules, so that when we get in the queue, we will follow the fastest timeout first out queue;

package com.example.demo.study;

import java.util.Random;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

import lombok.Data;

public class Study0210 {

    @Data
    static class MyDelayed implements Delayed {
        private long delayTime;//When the task needs to be delayed in the queue
        private long expire;//This time is the sum of the current time and the delay time, which is called the expiration time
        private String taskName;//Name of task

        public MyDelayed(long delayTime, String taskName) {
            this.delayTime = delayTime;
            this.taskName = taskName;
            this.expire = System.currentTimeMillis()+delayTime;
        }
        
        //Specify the comparison rules in the priority queue, just like the comparator in the priority queue in the previous blog
        @Override
        public int compareTo(Delayed o) {
            return (int)(this.getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS));
        }

        //This method indicates how much time the task has left in the queue, that is expire-current time
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(this.expire-System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //Create delay queue
        DelayQueue<MyDelayed> queue = new DelayQueue<MyDelayed>();
        
        //Create a task and drop it in the queue
        Random random = new Random();
        for (int i = 1; i < 11; i++) {
            MyDelayed myDelayed = new MyDelayed(random.nextInt(500),"task"+i);
            queue.add(myDelayed);
        }
        
        //Get the tasks in the queue, which is only related to the minimum timeout, and has nothing to do with the queue order
        MyDelayed myDelayed = queue.take();
        while(myDelayed!=null) {
            System.out.println(myDelayed.toString());
            myDelayed = queue.take();
        }
    }
}

 

 

 

2, Basic composition

//Therefore, the tasks stored in this queue must be Delayed Type
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
    //Exclusive lock
    private final transient ReentrantLock lock = new ReentrantLock();
    //Priority queue
    private final PriorityQueue<E> q = new PriorityQueue<E>();
    //leader Threads, in fact, the only way to enter and exit a queue is leader Threads, the rest are called fallower Thread, a leader-follower Pattern
    private Thread leader = null;
    //Conditional variable
    private final Condition available = lock.newCondition();

    //Omit a lot of code
}

 

  

See the following figure for the specific inheritance relationship. The actual operation is the internal PriorityQueue;

 

 

3, offer method

In the above code, we call the add method, but in fact, we call the offer method;

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    //Get lock
    lock.lock();
    try {
        //Add an element to the priority queue
        q.offer(e);
        //Be careful, peek Method just gets the first element in the priority queue and does not delete it
        //If the elements taken from the priority queue are the same as the currently added elements, it means that the current elements meet the expiration requirements, so the leader Thread for null
        //Then inform the thread priority queue in the condition queue that there are already elements in the queue, and you can come to get them
        if (q.peek() == e) {
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        //Release lock
        lock.unlock();
    }
}

 

 

4, take method

Get and remove the elements in the queue that meet the timeout requirements. If there is no element in the queue, the current thread will be dropped to the conditional queue to block;

We can know from the following code logic: there are two kinds of threads: one is leader thread, the other is follower thread, in which leader thread will only block for a certain time, and follower thread will block for an unlimited time in condition queue; when leader thread finishes taking operation, it will reset leader thread to null, and then take one out of condition queue Set as leader thread

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    //Acquire lock, interruptible
    lock.lockInterruptibly();
    try {
        for (;;) {
            //First, try to get the node from the priority queue. If not, it means that the current priority queue is empty, which blocks the current thread
            E first = q.peek();
            if (first == null)
                available.await();
            else {
                //If there is an element in the priority queue, you can definitely come here, and then take the timeout of the element. If it is less than 0, it means that the requirement has been met. You can get and delete the element in the queue
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0)
                    return q.poll();
                first = null; // don't retain ref while waiting
                //If leader The queue is not empty, indicating that another thread is executing take,The current thread is placed in the condition queue
                if (leader != null)
                    available.await();
                //Here, it means that there is no element in the priority queue to the timeout time, and no other thread calls at this time take Method, so leader The thread is set to the current thread,
                //Then the present leader The thread will wait for a certain time, waiting for the fastest timeout element in the priority queue;
                //While waiting, leader The thread will release the lock, while other threads B Can call offer Method add element, thread C You can also call take Method, and then thread C Will be in
                //It's blocked up here for an infinite amount of time until it wakes up
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);
                    } finally {
                        //After the current thread is blocked for a certain period of time, whether it succeeds or not, it will leader Thread reset to null,And then recycle
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    //This means that after the current thread successfully removes the element, the thread in the condition queue wakes up to continue to get the element from the queue
    } finally {
        if (leader == null && q.peek() != null)
            available.signal();
        //Release lock
        lock.unlock();
    }
}

 

 

5, poll operation

Get and remove the queue header expiration element. If the queue is empty, or the header element does not exceed the timeout, return null

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //Try to get the team head element. If the team head element is empty or the delay expires before, return null
        E first = q.peek();
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
        //Otherwise, get and remove the team head element
            return q.poll();
    } finally {
        lock.unlock();
    }
}

 

 

Six. Conclusion

In fact, this queue is very easy. The main thing is that there is a delay time. The root node we get from the priority queue will first determine whether there is a timeout time. If there is one, it's better to remove it and return it. If there is no one, it's better to see how much time is left before the timeout (because it's a priority queue, the root node is generally the fastest timeout time, of course, it's also You can modify the priority queue comparison rule), so the current thread will wait for this node to time out. At this time, the leader is equal to the current thread, and during the waiting process, the lock will be released, so other threads can add elements to the queue, or get elements (but because of this time, the leader! =null, these threads will block indefinitely for a long time until they are awakened);

After the timeout time of the leader thread is up, it will wake up automatically. After another cycle, the root node will be acquired and removed. Finally, reset the leader node to null, and wake up the nodes in the condition queue;

Tags: Java Lombok REST less

Posted on Tue, 11 Feb 2020 04:07:46 -0500 by gernot