DelayQueue源码

介绍

一个实现PriorityBlockingQueue实现延迟获取的无界队列,在创建元素时,可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。(DelayQueue可以运用在以下应用场景:1.缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。2.定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。)

数据结构

public interface Delayed extends Comparable<Delayed> {

    /**
     * 返回与此对象相关的剩余延迟时间,以给定的时间单位表示
     */
    long getDelay(TimeUnit unit);
}

 getDelay方法一般用内部存储的事件,减去当前事件,即为剩余延迟事件

属性

  private final transient ReentrantLock lock = new ReentrantLock();
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    /**
    *用于优化内部阻塞通知的线程
     */
    private Thread leader = null;
    private final Condition available = lock.newCondition();

以支持优先级的PriorityQueue无界队列作为一个容器,因为元素都必须实现Delayed接口,可以根据元素的过期时间来对元素进行排列,因此,先过期的元素会在队首,每次从队列里取出来都是最先要过期的元素。

leader是一个Thread元素,它在offer和take中都有使用,它代表当前获取到锁的消费者线程,

DelayQueue实现Leader-Folloer pattern
1、当存在多个take线程时,同时只生效一个,即,leader线程
2、当leader存在时,其它的take线程均为follower,其等待是通过condition实现的
3、当leader不存在时,当前线程即成为leader,在delay之后,将leader角色释放还原
4、最后如果队列还有内容,且leader空缺,则调用一次condition的signal,唤醒挂起的take线程,其中之一将成为新的leader
5、最后在finally中释放锁

方法实现

offer,poll,peek

    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            //如果插入元素是第一个元素
            if (q.peek() == e) {
                //leader设置为null
                leader = null;
                //唤醒
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    public boolean offer(E e, long timeout, TimeUnit unit) {
        return offer(e);
    }
    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();
        }
    }
   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();
                if (first == null) {
                    if (nanos <= 0)
                        return null;
                    else
                        nanos = available.awaitNanos(nanos);
                } else {
                    long delay = first.getDelay(NANOSECONDS);
                    //到期,则poll
                    if (delay <= 0)
                        return q.poll();
                    if (nanos <= 0)
                        return null;
                    first = null; // don't retain ref while waiting
                    if (nanos < delay || leader != null)//nanos<delay,表示超时剩余时间小于到期时间,
                        nanos = available.awaitNanos(nanos);
                    else {
                    
                        Thread thisThread = Thread.currentThread();
                        //设置当前线程为leader
                        leader = thisThread;
                        try {
                            //等待条件
                            long timeLeft = available.awaitNanos(delay);
                            //剩余超时时间
                            nanos -= delay - timeLeft;
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }
    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.peek();
        } finally {
            lock.unlock();
        }
    }
    

put,take

/**
     * Retrieves and removes the head of this queue, waiting if necessary
     * until an element with an expired delay is available on this queue.
     *
     * @return the head of this queue
     * @throws InterruptedException {@inheritDoc}
     */
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        // 获取可中断锁。
        lock.lockInterruptibly();
        try {
            for (;;) {
                // 从优先级队列中获取队列头元素
                E first = q.peek();
                if (first == null)
                    // 无元素,当前线程加入等待队列,并阻塞
                    available.await();
                else {
                    // 通过getDelay 方法获取延迟时间
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        // 延迟时间到期,获取并删除头部元素。
                        return q.poll();
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            // 线程节点进入等待队列 x 纳秒。
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            // leader == null且还存在元素的话,唤醒一个消费线程。
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }



   public void put(E e) {
        offer(e);
    }

take()方法逻辑:

1.获取锁
2.取出优先级队列q的首元素
3.如果元素q的队首/队列为空,阻塞
3.如果元素q的队首(first)不为空,获得这个元素的delay时间值,如果first的延迟delay时间值为0的话,说明该元素已经到了可以使用的时间,调用poll方法弹出该元素,跳出方法
4.如果first的延迟delay时间值不为0的话,释放元素first的引用,避免内存泄露
5.循环以上操作,直到return

leader作用

如果leader不为null,说明已经有消费者线程拿到锁,直接阻塞当前线程,如果leader为null,把当前线程赋值给leader,并等待剩余的到期时间,最后释放leader,这里我们想象着我们有个多个消费者线程用take方法去取,如果没有leader!=null的判断,这些线程都会无限循环,直到返回第一个元素,很显然很浪费资源。所以leader的作用是设置一个标记,来避免消费者的无脑竞争。

参考

猜你喜欢

转载自blog.csdn.net/demon7552003/article/details/90728111