[JUC source code] DelayQueue source code analysis

1. Structure

DelayQueue inheritance relationship, core member variables and main constructor:

// 队列中的元素都要实现Delayed接口,实现后每个实体对象就有过期时间了
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
	implements BlockingQueue<E>{
    
    
	
	// 组合PriorityQueue进行队列操作
    private final PriorityQueue<E> q = new PriorityQueue<E>();
	// reentrantLock保证线程安全
	private final transient ReentrantLock lock = new ReentrantLock();
	// 出队休眠Condition。其实很容易理解:当有线程来取元素,但队列中为空或者没有元素过期,所以要进入条件队列等待
	private final Condition available = lock.newCondition();
    // 条件队列中剩余等待时间最短的线程,一般是先来的线程
    // 注:虽然引入了leader,但是也并非公平的(1.非公平锁 2.当新线程能获取元素时就直接返回了)
	private Thread leader = null;
	
	//--------------------------------构造函数-------------------------------
	public DelayQueue() {
    
    }
		
    public DelayQueue(Collection<? extends E> c) {
    
    
        this.addAll(c);
    }
	
}
  • PriorityQueue is called a priority queue in Chinese. The function here is to sort the priority according to the expiration time, so that the first expired can be executed first. For details, please refer to [Java container source code] PriorityQueue source code analysis .
  • Reuse has always been a common and important topic, such as the ability of DelayQueue to reuse PriorityQueue, the ability of LinkedHashMap to reuse HashMap, and the ability of Set to reuse Map. To summarize, what needs to be done if you want to reuse:
    • It is necessary to abstract as much as possible the reusable functions and open up the extensibility. For example, in the method of manipulating the array of HashMap, many methods starting with after are opened for LinkedHashMap, which is convenient for LinkedHashMap to sort, delete, etc.;
    • Use combination or inheritance to reuse, such as the inheritance used by LinkedHashMap and the combination used by Set and DelayQueue. Combination means to rely on reusable classes.
  • <E extends Delayed>: That is, all elements in the queue should implement the Delayed interface. As can be seen from the source code below, Delayed also inherits the Comparable interface, so the elements in the queue should implement the following two methods:
    • getDelay: Get the expiration time. So it must be clear here that the expiration time of each element is determined before being put into the queue, rather than being passed in as a parameter when being put into the queue.
    • compareTo: sort all elements with the expiration time of getDelay
public interface Delayed extends Comparable<Delayed> {
    
    
    long getDelay(TimeUnit unit);
}

2. Method analysis & api

2.1 Join the team

put()

public void put(E e) {
    
    
    	// 调用offer
        offer(e);
}

offer()

The logic of offer is actually very simple

  1. Call the offer method of PriorityQueue to add new elements. Because the bottom layer of PriorityQueue is the smallest heap, new elements are floated up

  2. If this new element happens to be the head of the queue (top of the heap), then a waiting thread must be awakened immediately, and then the current leader will be deleted to give the new thread a chance to become the leader.

    The reason for this is that the leader must be the thread with the shortest remaining waiting time in the conditional queue (waiting time = expiration time for the head of the queue), but now a new head with a shorter expiration time has come, so there should be a shorter thread to come. Be the leader. If you don't understand this sentence, you can compare it with the take method below.

public boolean offer(E e) {
    
    
    final ReentrantLock lock = this.lock;
    lock.lock(); // 上锁
    try {
    
    
        // 直接调用 PriorityQueue 的 offer 方法,从而使用它的排序和扩容等能力
        q.offer(e);
        // 如果恰好刚放进去的元素正好在队列头
        // 立马唤醒一个在条件队列等待线程,并将现在leader置为null,让那个新唤醒的线程有机会成为leader
        if (q.peek() == e) {
    
    
            leader = null;
            available.signal();
        }
        return true;
    } finally {
    
    
        lock.unlock(); // 释放锁
    }
}

2.2 Departure

take()

The process of the take method is as follows:

  • The thread that got the lock can spin

  • Take out the team leader first, divided into the following two situations:

    • Case 1: first=null, the queue is empty, and the current thread is sleeping

    • Case 2: first != null, that is, there is an element in the queue, then it depends on whether the element expires

      • Situation 2.1: Expiration time delay<0, that is, the head of the line has expired, can be taken out, and return

      • Situation 2.2: The head of the queue has not expired, then the current thread must be put into the conditional queue to sleep and wait, but at this time, it is necessary to consider whether the current thread can be the leader

        • Situation 2.2.1: If the current thread is the first one to come, it will be stored in the leader, and then sleep periodically.
          Note: This does not guarantee fairness, nor does it guarantee that it will be the leader of the team after automatically waking up.

        • Situation 2.2.2: There is already a leader, the current thread sleeps directly

  • Release lock

public E take() throws InterruptedException {
    
    
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly(); // 加锁,可中断
        try {
    
    
            // 自旋 保证一定能成功
            for (;;) {
    
    
                // 拿出队首first
                E first = q.peek();
                // 情况 1:first=null,队列为空,休眠当前线程
                if (first == null)
                    available.await();
                // 情况 2:队列非空
                else {
    
    
                    long delay = first.getDelay(NANOSECONDS); // 获取队首元素(first)的过期时间delay
                    // 情况 2.1:first已经过期,则可以出队
                    if (delay <= 0)
                        return q.poll();
                    first = null; // 将first引用置为 null ,便于gc
                    // 情况 2.2:first未过期
                    // 情况 2.2.1:leader!=null,即在当前线程之前已经有线程等待取出了,所以直接休眠当前线程
                    if (leader != null)
                        available.await();
                    // 情况 2.2.2:leader == null,即当前线程当前线程是第一个要来取出的线程,存入leader
                    else {
    
    
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
    
    
                            // 给leader计时休眠,释放锁
                            // 到时自动醒来,大概率取得队首
                            available.awaitNanos(delay);
                        } finally {
    
    
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
    
    
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();  // 释放锁
        }
}

poll()

Compared to take, poll will directly return null when the queue is empty or the head of the queue has not expired, instead of blocking there

public E poll() {
    
    
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
    
    
            E first = q.peek();
            // !!!如果队列为空,或队首没过期,直接放回null
            if (first == null || first.getDelay(NANOSECONDS) > 0)
                return null;
            else
                return q.poll();
        } finally {
    
    
            lock.unlock();
        }
}

In addition, if you don’t want to block indefinitely, you can call poll to set the blocking time
Insert picture description here

2.3 Get the leader of the team: peek

public E peek() {
    
    
    	// 虽然只是查看队首,但也要获取锁,防止在查看时被修改
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
    
    
            // 调用priortyQueue的peek
            return q.peek();
        } finally {
    
    
            lock.unlock();
        }
}

3. Use case

public class DelayQueueDemo {
    
    
    
  @Data
  // 队列元素,实现了 Delayed 接口
  static class DelayedDTO implements Delayed {
    
    
    Long s;
    Long beginTime;
    public DelayedDTO(Long s,Long beginTime) {
    
    
      this.s = s;
      this.beginTime =beginTime;
    }

    // 重写getDelay,获取过期时间
    public long getDelay(TimeUnit unit) {
    
    
      return unit.convert(s - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    // compareTo
    public int compareTo(Delayed o) {
    
    
      return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }
  }
    
    
  // 生产者
  static class Product implements Runnable {
    
    
    // DelayQueue是BlockQueue的实现类,因此这里使用接口更灵活
    private final BlockingQueue queue;
    public Product(BlockingQueue queue) {
    
    
      this.queue = queue;
    }
    
    @Override
    public void run() {
    
    
      try {
    
    
        log.info("begin put");
        long beginTime = System.currentTimeMillis();
        // 放入队列,延迟 2 秒执行
        queue.put(new DelayedDTO(System.currentTimeMillis() + 2000L,beginTime));
        // 延迟 5 秒执行
        queue.put(new DelayedDTO(System.currentTimeMillis() + 5000L,beginTime));
        // 延迟10秒执行
        queue.put(new DelayedDTO(System.currentTimeMillis() + 1000L * 10,beginTime));
        log.info("end put");
      } catch (InterruptedException e) {
    
    
        log.error("" + e);
      }
    }
  }
    
  // 消费者
  static class Consumer implements Runnable {
    
    
    private final BlockingQueue queue;
    public Consumer(BlockingQueue queue) {
    
    
      this.queue = queue;
    }

    @Override
    public void run() {
    
    
      try {
    
    
        log.info("Consumer begin");
        // 从队列中取
        ((DelayedDTO) queue.take()).run();
        ((DelayedDTO) queue.take()).run();
        ((DelayedDTO) queue.take()).run();
        log.info("Consumer end");
      } catch (InterruptedException e) {
    
    
        log.error("" + e);
      }
    }
  }

  // Mian
  public static void main(String[] args) throws InterruptedException {
    
    
    BlockingQueue q = new DelayQueue();
    // 将delayQueue传入生产者与消费者
    DelayQueueDemo.Product p = new DelayQueueDemo.Product(q);
    DelayQueueDemo.Consumer c = new DelayQueueDemo.Consumer(q);
    new Thread(c).start();
    new Thread(p).start();
  }
}

The execution results are as follows:

06:57:50.544 [Thread-0] Consumer begin
06:57:50.544 [Thread-1] begin put
06:57:50.551 [Thread-1] end put
06:57:52.554 [Thread-0] 延迟了2秒钟才执行
06:57:55.555 [Thread-0] 延迟了5秒钟才执行
06:58:00.555 [Thread-0] 延迟了10秒钟才执行
06:58:00.556 [Thread-0] Consumer end

Guess you like

Origin blog.csdn.net/weixin_43935927/article/details/108858129