Bootstrap

DelayQueue学习

本文从Think in java(实例copy) +API源码(翻译分析)+自己理解进行讲述的,不足之处清指出

1.introduce:主要是原理介绍,如若想直接上手,看第二部分例子就可以了,推荐还是先看懂第一部分

API介绍

An unbounded {@linkplain BlockingQueue blocking queue} of {@code Delayed} elements, in which an element can only be taken when its delay has expired
第一句话介绍DelayQueue是一个无界队列,并且里面的所有元素都只有过期的时候才能被取出

If no delay has expired there is no head and {@code poll} will return {@code null}
试想如果没有一个deley元素过期,那么队列就不会有头元素(因为没有过期对象,所以取不出头元素),所以poll(效果:取出头元素并删除)将返回null
tips:因此本队列不能存放null值,因为在offer方法中进行了判空,但如果没有判空会造成什么影响呢?

Expiration occurs when an element’s method{getDelay(TimeUnit.NANOSECONDS)} returns a value less
than or equal to zero
是否过期是通过方法getDelay返回是否小于等于0来判断的

Even though unexpired elements cannot be removed using {@code take} or {@code poll}, they are otherwise
treated as normal elements. For example, the {@code size} method returns the count of both expired and unexpired elements.
尽管没有过期的元素不能通过take或者poll方法移除,但是他们和过期的没有什么不同,比如size方法返回的大小是包含了过期与没过期的。

内部成员变量介绍(东西有点多,请一步一步慢慢来)
/**
 *此类继承自AbstractQueue则拥有了add和clear方法,实现BlockingQueue则它是一个阻塞队列且有用offer和remove等方法
 */
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
    /*定义一个不可变不会被序列化的重入锁,说明可能内部存取是通过lock,unlock同步的*/
    private final transient ReentrantLock lock = new ReentrantLock();
    /*内部维护一个优先级队列,因为有效期最长的一定最后出来,那么有效期的长短就是优先级的先后顺序*/
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    /**下面来一个非常有意思的成员leader,不多废话,直接上直译
     * 这个线程是被用来设计等待队列的头元素的。采用领导追随者网络模型来减少不必要的等待时间(感觉有点稀里糊涂的呢,
     * 不用管,知道结论就可以了,稍微想有所了解的可以看文章末尾 注:1处)
     * 当一个线程成为领导者的时候,它只用等待它下一个延迟消逝时间,其他线程全部进入无期限长等待。领导者线程必须
     * 在take或者poll操作之前发送提醒信息给其他线程,除非其他线程已经变为领导者了,无论何时,当队列头元素被已经
     * 到期的其他元素代替的时候,领导者域都会被置为null,且其他等待线程,除了当前领导者外的线程都会被唤醒。所以
     * 等待中的线程必须准备好在等待中随时接受和失去领导者*/
    private Thread leader;
    /*一个condition对象,拥有signal和await方法(通俗可理解为notifyAndWait)*/
    private final Condition available = lock.newCondition();
}
常用方法介绍(原理是基础的第一步)
1. offer方法
public boolean offer(E e) {
    //获得重入锁对象并加锁
       final ReentrantLock lock = this.lock; 
       lock.lock();
       try {
           //向优先级队列q添加任务
           q.offer(e);//感觉和ArrayList差不多(1.检查非空,容量2.超容了,扩容,数组拷贝)
           /**
            *优先级队列内部维护一个Object数据,peek方法返回数据的第一个元素,若size等于0则返回null
            *如果当前添加的元素是在第一个位置,说明要么队列中只有它一个,要么他的到期时间最快,
            *将leader线程置为null,且唤醒所有的追随者
           */`这里写代码片`
           if (q.peek() == e) { 
               leader = null;
               available.signal();
           }
           return true;
       } finally {
           lock.unlock();
       }
   }
2. poll方法
 public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E first = q.peek();
            //这方法简单太多了,第一个元素没到期或者为null就返回null,否则从优先级object数组中取出头元素
            return (first == null || first.getDelay(NANOSECONDS) > 0)
                ? null
                : q.poll();//有点想弄明白优先级队列中的的可以看下注释**PriorityQueue的poll**:
        } finally {
            lock.unlock();
        }
    }
/****************************************我是分界线*********************************************/    
 //PriorityQueue的poll方法
  public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++; //代表这个优先级队列结构被修改了多少次
        E result = (E) queue[0]; //返回头元素
        E x = (E) queue[s];
        queue[s] = null; //防止内存溢出
        if (s != 0)
        /**
         *最小堆算法,这里只需知道结论就可以了,每一次poll操作,都会通过siftDown内部方法(compareto比较)将最小
         *值放在头部,但是不保证除开头部以外的也是按照compareTo来进行排序的
         */
        siftDown(0, x);
        return result; //return返回的都是队列中的最小值
    }
3.take方法(因为用到了leader)
 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) { //可以理解为while(true)
                E first = q.peek();
                if (first == null) //若当前优先级队列没有元素,则等待
                    available.await();
                else { //其实整个else就是在不断for循环,直到到达delay时间
                    long delay = first.getDelay(NANOSECONDS);//参数纳秒时间-当前纳秒时间
                    if (delay <= 0L) //如果任务延时时间已到,就直接返回
                        return q.poll();
                    /**
                     * 如果任务延时还没到,由于在for(;;)死循环中,q.peek()一直再取头元素,为了防止内存溢出
                     * 所以first=null
                     */
                    first = null;
                    if (leader != null)//说明还没有线程去处理事件
                        available.await();
                    else { //如果领导者线程去执行具体事情了,然后当前线程就变为领导者
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);//让其等待对应的延时事件后,在返回for循环判断
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal(); //poll元素成功后,唤醒前面所有等待线程,释放锁
            lock.unlock();
        }
    }
  1. 注1: 领导者追随者模型 : 增强CPU高速缓存相似性,及消除动态内存分配和线程间的数据交换
    具体可以参考这个人的博客http://blog.csdn.net/goldlevi/article/details/7705180
    有若干个线程(一般组成线程池)用来处理大量的事件
    有一个线程作为领导者,等待事件的发生;其他的线程作为追随者,仅仅是睡眠。
    假如有事件需要处理,领导者会从追随者中指定一个新的领导者,自己去处理事件。
    唤醒的追随者作为新的领导者等待事件的发生。
    处理事件的线程处理完毕以后,就会成为追随者的一员,直到被唤醒成为领导者。
    假如需要处理的事件太多,而线程数量不够(能够动态创建线程处理另当别论),则有的事件可能会得不到处理。
;