DelayQueue

概述

DelayQueue是一个支持时延获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
DelayQueue可以运用在以下两个应用场景:

  1. 缓存系统的设计:使用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,就表示有缓存到期了。
  2. 定时任务调度:使用DelayQueue保存当天要执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如Tiner就是使用DelayQueue实现的。

用法实例

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

/**
 * @author admin
 */
public class Message implements Delayed {
    /**
     *触发时间
     */
    private long time;

    /**
     *名称
     */
    String name;
    public Message(String name,long time,TimeUnit unit){
        this.name = name;
        this.time = System.currentTimeMillis() + (time > 0 ? unit.toMillis(time) : 0);
    }
    @Override
    public long getDelay(TimeUnit unit) {
        return time - System.currentTimeMillis();
    }

    @Override
    public int compareTo(Delayed o) {
        Message item = (Message) o;
        long diff = this.time - item.time;
        if (diff <= 0){
            return  -1;
        }else{
         return 1;
        }
    }

    @Override
    public String toString() {
        return DelayQueueDemo.printDate() + "Message{" + "time=" + time + ", name=" + name + "/" + "}";
    }
}

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author admin
 */
public class DelayQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        Message item1 = new Message("消息1",5, TimeUnit.SECONDS);
        Message item2 = new Message("消息2",10, TimeUnit.SECONDS);
        Message item3 = new Message("消息3",15, TimeUnit.SECONDS);

        DelayQueue<Message> queue  = new DelayQueue<Message>();
        queue.add(item1);
        queue.add(item2);
        queue.add(item3);

        int queueLengh = queue.size();
        System.out.println(printDate() + "开始!");
        for (int i = 0; i < queueLengh; i++) {
            Message take = queue.take();
            System.out.format(printDate() + " 消息出队,属性name=%s%n",take.name);
        }

        System.out.println(printDate() + "结束!");
    }
    static String printDate(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(new Date());
    }
}

DelayQueue声明

DelayQueue声明如下:

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E>

从DelayQueue声明可以看出,DelayQueue中的元素必须是Delayed接口的子类。

Delayed声明如下:

public interface Delayed extends Comparable<Delayed> {
	/**
	*以给定的时间单位返回与此对象关联的剩余延迟
	*/
    long getDelay(TimeUnit unit);
}

DelayQueue属性

/**
*可重入锁
*/
private final transient ReentrantLock lock = new ReentrantLock();
/**
*缓存元素的优先级队列
*/
private final PriorityQueue<E> q = new PriorityQueue<E>();
/**
*特定的用于等待队列头中元素的线程
*Leader-Follower模式的变体形式
*用于最小化不必要的定时等待
*/
private Thread leader = null;
/**
*当更新的元素在队列的开头变得可用时
*或在新线程可能需要成为领导者时,会发出条件信号
*/
private final Condition available = lock.newCondition();

以上可以看出,延时队列主要使用优先级队列来实现,并辅以重入锁和条件来控制并发安全。

DelayQueue构造器

/**
*默认构造器
*/
public DelayQueue() {}
/**
*添加集合c中所有元素到队列中
*/
public DelayQueue(Collection<? extends E> c) {
	this.addAll(c);
}

DelayQueue入队

/*
*将指定元素插入此延时队列
*/
public boolean add(E e) {
	return offer(e);
}
/*
*将指定元素插入此延时队列
*由于队列是无界的,因此该方法将永远不会被阻塞
*/
public void put(E e) {
	offer(e);
}
/*
*将指定元素插入此延时队列
*由于队列是无界的,因此该方法将永远不会被阻塞
*/
public boolean offer(E e, long timeout, TimeUnit unit) {
	return offer(e);
}

以上几个方法都会调用offer()方法。

    public boolean offer(E e) {
    	//获取可重入锁
        final ReentrantLock lock = this.lock;
        //可重入锁加锁
        lock.lock();
        try {
        	//调用优先级队列的offer()方法入队
            q.offer(e);
            //如果入队元素在队首,则唤醒一个出队线程
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            //返回入队成功
            return true;
        } finally {
        	//解锁
            lock.unlock();
        }
    }

leader是等待获取队列头元素的线程,应用主从式设计减少不必要的等待。如果leader不为空,表示已经有线程在等待获取队列的头元素。所以,通过await()方法让出当前线程等待信号。如果leader为空,则把当前线程设置为leader,当一个线程为leader,它使用awaitNanos()方法让当前线程等待接收信号或等待delay时间。

DelayQueue出队

poll()方法

	/*
	*检索并删除次队列的头
	*如果此队列没有延迟过期的元素,则返回null
	*/
    public E poll() {
    	//获取可重入锁
        final ReentrantLock lock = this.lock;
        //可重入锁加锁
        lock.lock();
        try {
        	//检索但不删除队列头部元素
            E first = q.peek();
            //如果first为null或者返回与此对象关联的剩余延迟时间大于0
            //返回null
            if (first == null || first.getDelay(NANOSECONDS) > 0)
                return null;
            else
            	//否则通过优先队列poll()方法出队
                return q.poll();
        } finally {
        	//可重入锁解锁
            lock.unlock();
        }
    }

take()方法

	/*
	*检索并除去此队列的头
	*等待直到该队列上具有过期延迟的元素可用
	*/
    public E take() throws InterruptedException {
    	//获取可重入锁
        final ReentrantLock lock = this.lock;
        //可重入锁加锁
        lock.lockInterruptibly();
        try {
            for (;;) {
            	//检索但不删除队列头部元素
                E first = q.peek();
                //如果first为空
                if (first == null)
                	//在available条件上等待
                    available.await();
                else {
                	//如果first非空
                	//获取first的剩余延迟时间
                    long delay = first.getDelay(NANOSECONDS);
                    //如果delay小于等于0
                    if (delay <= 0)
                    	//延迟时间到期,获取并删除头部元素
                        return q.poll();
                    //如果delay大于0,即延迟时间未到期
                    //将first置为null
                    first = null; 
                    //如果leader线程非空
                    if (leader != null)
                    	//当前线程无限期阻塞
                    	//等待leader线程唤醒
                        available.await();
                    else {
                    	//如果leader线程为空
                    	//获取当前线程
                        Thread thisThread = Thread.currentThread();
                        //是当前线程成为leader线程
                        leader = thisThread;
                        try {
                        	//当前线程等待剩余延迟时间
                            available.awaitNanos(delay);
                        } finally {
                        	//如果当前线程是leader线程
                        	//释放leader线程
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
        	//如果leader线程为null并且队列不为空
        	//说明没有其他线程在等待,那就通知条件队列
            if (leader == null && q.peek() != null)
            	//通过signal()方法唤醒一个出队线程
                available.signal();
            //解锁
            lock.unlock();
        }
    }

take()方法总结:

  1. 获取锁。
  2. 取出优先级队列q的首元素。
  3. 如果元素q的队首为空则阻塞。
  4. 如果元素q的队首(first)不为空;获取这个元素的delay时间值,如果first的延迟delay时间小于等于0,说明元素已经到了可以使用的时间,调用poll()方法弹出该元素,跳出方法。
  5. 如果first的延迟delay时间大于0,释放元素first的引用,避免内存泄漏。
  6. 如果延迟delay时间大于0,leader非空,当前线程等待。
  7. 如果延迟delay时间大于0,leader为空,将当前线程设置为leader线程,等待剩余时间。
  8. 自旋,循环以上操作,直到return。

重载poll()方法

    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();
                //如果first为空
                if (first == null) {
                	//如果nanos小于等于0
                    if (nanos <= 0)
                    	//返回null
                        return null;
                    else
                    	//如果nanos大于0
                    	//等待nanos时间
                        nanos = available.awaitNanos(nanos);
                } else {
                	//如果队首非空
                	//获取first的剩余延迟时间
                    long delay = first.getDelay(NANOSECONDS);
                   	//如果delay小于等于0
                    if (delay <= 0)
                    	//延迟时间到期,获取并删除头部元素
                        return q.poll();
                    //如果delay大于0
                    //如果nanos小于等于0
                    if (nanos <= 0)
                    	//返回null
                        return null;
                    //如果delay大于0且nanos大于0
                    //first置为null
                    first = null; 
                    //如果nanos小于delay或者leader非空
                    if (nanos < delay || leader != null)
                    	//等待delay时间
                        nanos = available.awaitNanos(nanos);
                    else {
                    	//如果nanos大于等于delay或者leader为空
                    	//获取当前线程
                        Thread thisThread = Thread.currentThread();
                        //设置当前线程为leader
                        leader = thisThread;
                        try {
                        	//等待delay时间
                            long timeLeft = available.awaitNanos(delay);
                            //修改nanos
                            nanos -= delay - timeLeft;
                        } finally {
                        	//如果当前线程为leader线程
                        	//释放leader线程
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
        	//如果leader为null并且队列不为空
        	//说明没有其他线程在等待,那就通知条件队列
            if (leader == null && q.peek() != null)
            	//通过singnal()方法唤醒一个出队线程
                available.signal();
            //解锁
            lock.unlock();
        }
    }
相关推荐
<p> <span style="font-size:14px;color:#E53333;">限时福利1:</span><span style="font-size:14px;">购课进答疑群专享柳峰(刘运强)老师答疑服务</span> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;"></span> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>为什么需要掌握高性能的MySQL实战?</strong></span> </p> <p> <span><span style="font-size:14px;"><br /> </span></span> <span style="font-size:14px;">由于互联网产品用户量大、高并发请求场景多,因此对MySQL的性能、可用性、扩展性都提出了很高的要求。使用MySQL解决大量数据以及高并发请求已经是程序员的必备技能,也是衡量一个程序员能力和薪资的标准之一。</span> </p> <p> <br /> </p> <p> <span style="font-size:14px;">为了让大家快速系统了解高性能MySQL核心知识全貌,我为你总结了</span><span style="font-size:14px;">「高性能 MySQL 知识框架图」</span><span style="font-size:14px;">,帮你梳理学习重点,建议收藏!</span> </p> <p> <br /> </p> <p> <img alt="" src="https://img-bss.csdnimg.cn/202006031401338860.png" /> </p> <p> <br /> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>【课程设计】</strong></span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;">课程分为四大篇章,将为你建立完整的 MySQL 知识体系,同时将重点讲解 MySQL 底层运行原理、数据库的性能调优、高并发、海量业务处理、面试解析等。</span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;"></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>一、性能优化篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括经典 MySQL 问题剖析、索引底层原理和事务与锁机制。通过深入理解 MySQL 的索引结构 B+Tree ,学员能够从根本上弄懂为什么有些 SQL 走索引、有些不走索引,从而彻底掌握索引的使用和优化技巧,能够避开很多实战中遇到的“坑”。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>二、MySQL 8.0新特性篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括窗口函数和通用表表达式。企业中的许多报表统计需求,如果不采用窗口函数,用普通的 SQL 语句是很难实现的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>三、高性能架构篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括主从复制和读写分离。在企业的生产环境中,很少采用单台MySQL节点的情况,因为一旦单个节点发生故障,整个系统都不可用,后果往往不堪设想,因此掌握高可用架构的实现是非常有必要的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>四、面试篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">程序员获得工作的第一步,就是高效的准备面试,面试篇主要从知识点回顾总结的角度出发,结合程序员面试高频MySQL问题精讲精练,帮助程序员吊打面试官,获得心仪的工作机会。</span> </p>
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页