美文网首页
延时队列(DelayQueue)探秘

延时队列(DelayQueue)探秘

作者: CodingRunning | 来源:发表于2021-03-06 22:13 被阅读0次
    DelayQueue类图架构.jpg

    由上面的UML图可知,DelayQueue依赖于PriorityQueue,使用PriorityQueue存储对象,所以要彻底搞懂DelayQueue必须先知道PriorityQueue原理.

    PriorityQueue

    架构
    PriorityQueue架构图.jpg
    基本属性
    //默认初始容量
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    
    /**
     * Priority queue represented as a balanced binary heap:
     * 优先级队列代表了一个平衡二叉堆
     * the two children of queue[n] are queue[2*n+1] and queue[2*(n+1)].
     * queue[n]的两个孩子分别为queue[2*n+1]和queue[2*(n+1)]
     * The priority queue is ordered by comparator, or by the elements' natural ordering,
     * 优先级队列使用comparator或者元素的自然排序
     * if comparator is null:
     * 如果comparator为空
     * For each node n in the heap and each descendant d of n, n <= d.
     * 对于堆中的每个节点n和n的每个后代d,n<=d.
     * The element with the lowest value is in queue[0], assuming the queue is nonempty.
     * 如果队列不为空,则最小的元素在队列的第一位
     */
    transient Object[] queue;
    
    /**
     * The comparator, or null if priority queue uses elements' natural ordering.
     * 比较器,如果为空则使用元素的自然排序
     */
    private final Comparator<? super E> comparator;
    
    //集合操作次数(除去查询),主要用于fast-fail
    transient int modCount = 0;
    
    构造器
    //绝大部分情况下,都是用该构造器 
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
    
    //该构造器初始化队列
    public PriorityQueue(int initialCapacity,
                             Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }
    
    常用方法

    添加元素

    Add.jpg
    public boolean add(E e) {
        return offer(e);
    }
    
     public boolean offer(E e) {
         //不允许有空元素
         if (e == null)
             throw new NullPointerException();
         modCount++;
         int i = size;
         //数组已满,需要扩容
         if (i >= queue.length)
             grow(i + 1);
         //新元素个数
         size = i + 1;
         //数组还未插入元素,直接插入到第一个位置
         if (i == 0)
             queue[0] = e;
         else
             //数组第一个位置已经插入了元素,需要向堆中插入元素
             siftUp(i, e);
         return true;
     }
    
    private void grow(int minCapacity) {
        //旧数组容量
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        //如果旧容量小于64则新容量为旧容量的一倍+2.否则扩容50%
        int newCapacity = oldCapacity +
            ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1));
        // overflow-conscious code
        //如果minCapacity大于Integer.MAX_VALUE-8则重新计算新容量,最大为Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //数组拷贝,将旧数组扩容并将旧数组数据迁移
        queue = Arrays.copyOf(queue, newCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
    }
    
    private void siftUp(int k, E x) {
        //比较器不为空,按照比较器的规则插入
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            //比较器为空,按照自然排序插入
            siftUpComparable(k, x);
    }
    
    private void siftUpComparable(int k, E x) {
        //自然排序器
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            //寻找当前节点的父节点,这一步详情看上面注释Object[] queue;
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            //使用自然比较器与父节点比较,如果当前节点大于当前遍历节点的父节点,直接退出,这一步主要是为了维持小顶堆的性质
            if (key.compareTo((E) e) >= 0)
                break;
            //父节点下沉
            queue[k] = e;
            //向上移动
            k = parent;
        }
        queue[k] = key;
    }
    

    删除元素

    remove.jpg
    public boolean remove(Object o) {
        int i = indexOf(o);
        if (i == -1)
            return false;
        else {
            removeAt(i);
            return true;
        }
    }
    
    private E removeAt(int i) {
        // assert i >= 0 && i < size;
        modCount++;
        int s = --size;
        //要删除的为最后一个节点直接删除
        if (s == i) // removed last element
            queue[i] = null;
        else {
            //获取最后一个元素(最大的元素)
            E moved = (E) queue[s];
            //删除最后一个元素
            queue[s] = null;
            //将moved插入到i位置,期间会调整i的子堆
            siftDown(i, moved);
            //如果i的位置就是moved则表示moved子树元素都小,则需要继续调整堆
            if (queue[i] == moved) {
                //从i开始向上调整
                siftUp(i, moved);
                //当前位置就是moved的合适位置(moved继续上移),直接返回moved
                if (queue[i] != moved)
                    return moved;
            }
        }
        return null;
    }
    
    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }
    
    private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        //第一个叶子节点的下标
        int half = size >>> 1;        // loop while a non-leaf
        //从当前节点向叶子节点遍历
        while (k < half) {
            //左孩子
            int child = (k << 1) + 1;
            Object c = queue[child];
            //右孩子
            int right = child + 1;
            //如果右孩子不是最后一个节点并且左孩子比右孩子大,找出较小的孩子
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            //子节点向上移动
            queue[k] = c;
            //向下移动
            k = child;
        }
        queue[k] = key;
    }
    
    总结

    由上面的分析可知PriorityQueue采用小顶堆存储数据,并且采用Comparator和Comparable两种方式保持元素的优先级,但是PriorityQueue不是线程安全的,也没有提供获取元素的方式,只能通过contains来获取是否存在,通过iterator来遍历元素.

    DelayQueue

    基本属性
    //锁,保证线程安全
    private final transient ReentrantLock lock = new ReentrantLock();
    
    //存储元素使用的优先级队列
    private final PriorityQueue<E> q = new PriorityQueue<E>();
    
     /**
      * Thread designated to wait for the element at the head of the queue.
      * 指定在队列头等待元素的线程.
      * This variant of the Leader-Follower pattern serves to minimize unnecessary timed waiting.
      * 这种Leader-Follower模式的变体用于最小化不必要的定时等待。
      * When a thread becomes the leader, it waits only for the next delay to elapse,
      * but other threads await indefinitely.
      * 当一个线程成为leader线程时,它只等待下一个延迟过去,而其他线程则无限期地等待.
      * The leader thread must signal some other thread before returning from take() or
      * poll(...), unless some other thread becomes leader in the interim.
      * 前导线程必须在从take()或poll(...)返回之前通知其他线程,除非其他线程在此期间成为前导线程.
      * Whenever the head of the queue is replaced with an element with an earlier expiration time,
      * the leader field is invalidated by being reset to null, and some waiting thread,
      * but not necessarily the current leader, is signalled.
      * 每当队列的头被一个过期时间较早的元素替换,leader字段就会被重置为null而失效,并且一些等待的线程(不一定是当前的leader)会收到信号.
      * So waiting threads must be prepared to acquire and lose leadership while waiting.
      * 因此,等待线程必须准备好在等待过程中获得和失去领导地位.
      */
    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 DelayQueue() {}
    
    常用方法

    添加元素

    public boolean add(E e) {
        return offer(e);
    }
    
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //插入元素,此时插入的元素不一定在优先级队列的头部
            q.offer(e);
            //如果当前元素在优先级队列的头部,需要清除掉leader线程,激活available队列中的一个线程
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }
    

    移除元素

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //先锁定,如果被中断,该线程会抛出InterruptedException,详情请查阅AQS源码
        lock.lockInterruptibly();
        try {
            for (;;) {
                //获取优先级队列中的根节点,如果为空,将当前线程放入到available条件队列中
                E first = q.peek();
                if (first == null)
                    available.await();
                else {
                    //如果根节点不为空,获取当前节点的过期时间
                    long delay = first.getDelay(NANOSECONDS);
                    //如果已过期,直接出队
                    if (delay <= 0)
                        return q.poll();
                    //当前线程不是leader线程,继续阻塞进入到条件队列
                    if (leader != null)
                        available.await();
                    else {
                        //leader线程为null,设置当前线程为leader线程
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            //当前线程继续等待delay时间,在此期间该线程会释放锁,其他线程可以继续执行offer和take操作
                            //时间到后,该线程会继续尝试获取锁,继续执行上面的操作
                            available.awaitNanos(delay);
                        } finally {
                            //执行完毕后重置leader线程
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            //如果优先级队列中还有元素,并且leader为空,则唤醒条件队列中的线程
            if (leader == null && q.peek() != null)
                available.signal();
            //释放锁
            lock.unlock();
        }
    }
    
    总结

    DelayQueue采用PriorityQueue存储元素,采用ReentrantLock实现线程同步,采用Delayed接口获取剩余时间来实现一个优先级队列.

    相关文章

      网友评论

          本文标题:延时队列(DelayQueue)探秘

          本文链接:https://www.haomeiwen.com/subject/etasqltx.html