并发容器(四) — DelayQueue 源码解析

一、概述

DelayQueue 是一个无界阻塞队列,它提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素 (通过最小堆实现)。

DelayQueue 队列本身不具备存储能力,而是在内部通过 PriorityQueue 队列来实现存储功能和获取最近过期元素功能。

PriorityQueue 队列实现方案跟 PriorityBlockingQueue 相同,只是没有提供锁来保证并发安全。

本文涉及的知识点:

  1. 阻塞队列的概念:并发容器(一) — 综述
  2. 阻塞功能的实现涉及Condition接口:Condition接口
  3. 涉及的数据结构:二叉堆 - 最小堆
  4. 优先级队列:PriorityBlockingQueue(优先队列)

二、源码解析

关于队列的其他操作,参考:PriorityBlockingQueue(优先队列)

下面只分析如下几个过程:

  1. 入队阻塞
  2. 出队阻塞
  3. 延迟队列的功能如何实现
  4. 示例

1. 入队

/*
 * 当前获取锁的线程
 * Leader-Follower pattern
 */
private Thread leader = null;

/**
 * Condition signalled when a newer element becomes available
 * at the head of the queue or a new thread may need to
 * become leader.
 */
private final Condition available = lock.newCondition();
    
public void put(E e) {
    offer(e);
}

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        q.offer(e); //这里q(PriorityQueue)是线程不安全的优先队列 
        // 队列头的元素就是刚插入的元素
        if (q.peek() == e) { //peek()获取队列头的元素(不移除队列头)
        	//老的leader置为null (因为老的leader是目标取走前一个队头的线程),再让接下来最先来取走队头的线程成为leader
            leader = null;
            available.signal(); //通知获取数据时等待的线程
        }
        return true;
    } finally {
        lock.unlock();
    }
}

2. 出队

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {//自旋:循环获取数据,直到有数据到达延时时间,可以出队列为止。
            E first = q.peek(); //获取优先队列的头元素
            if (first == null) //如果成立,说明队列为空
                available.await(); //让获取数据的线程进入等待队列
            else {	//执行到这里,说明队列不为空
            	//这里获取的时间表示延迟的截止时间跟当前时间差。小于等于0表示延迟时间到了
                long delay = first.getDelay(NANOSECONDS); 
                if (delay <= 0)
                    return q.poll(); //poll()会将队列头元素移除队列
                available.awaitNanos(delay);
                // 执行到这里,说明还没有元素到达延迟时间,将first引用置空。
                first = null; // don't retain ref while waiting
                if (leader != null) //如果leader不为空,说明之前有线程在等待获取数据,所以当前线程是follower
                    available.await();
                else {
                	// 当前没有线程要获取数据,所以将当前线程选举为leader
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                    	//让当前线程进入等待队列等待剩余时间(避免频繁循环,消耗CPU),并释放锁。当这个等待时间抵达时,当前线程会重新获取到锁。
                        available.awaitNanos(delay);
                    } finally {
                    	// 由于available.awaitNanos(delay)返回时获取了锁,且进入下一个循环肯定能拿到队列头元素,所以当前线程不再是leader。
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
    	// leader == null表示当前没有获取数据的线程。
    	// q.peek() != null 表示队列还有数据,可以通知其他等待线程来获取。
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

3. 延迟功能的实现

/**
 * 1.利用 PriorityQueue 队列堆顶最先达到延迟时间的特点。
 * 2.获取堆顶元素,判断延时时间是否抵达,抵达则出队,否则进入下个循环。
 */
public E take() {
    for (;;) {
        E first = queue.peek(); //queue是PriorityQueue对象
        if (first == null) 
            continue; //队列为空,进入下个循环
            
        long delay = first.getDelay(NANOSECONDS); 
        if (delay <= 0) {
            return queue.poll();  
    }
}

发布了158 篇原创文章 · 获赞 26 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Love667767/article/details/104967420