Java 集合框架分析:DelayQueue java8

相关文章:
Java 集合框架分析:Set
http://blog.csdn.net/youyou1543724847/article/details/52733723
Java 集合框架分析:LinkedList
http://blog.csdn.net/youyou1543724847/article/details/52734935
Java 集合框架分析:DelayQueue
http://blog.csdn.net/youyou1543724847/article/details/52176504
Java 集合框架分析:ArrayBlockingQueue
http://blog.csdn.net/youyou1543724847/article/details/52174308
Java 集合框架分析:ArrayDeque
http://blog.csdn.net/youyou1543724847/article/details/52170026
Java 集合框架分析:PriorityBlockingQueue
http://blog.csdn.net/youyou1543724847/article/details/52166985
Java 集合框架分析:JAVA Queue源码分析
http://blog.csdn.net/youyou1543724847/article/details/52164895
Java 集合框架分析:关于Set,Map集合中元素判等的方式
http://blog.csdn.net/youyou1543724847/article/details/52733766
Java 集合框架分析:ConcurrentModificationException
http://blog.csdn.net/youyou1543724847/article/details/52733780
Java 集合框架分析:线程安全的集合
http://blog.csdn.net/youyou1543724847/article/details/52734876
Java 集合框架分析:JAVA集合中的一些边边角角的知识
http://blog.csdn.net/youyou1543724847/article/details/52734918

DelayQueue
目录
DelayQueue简单说明
主要方法分析
比较
总结


DelayQueue简单说明

无界的阻塞队列,和普通的队列不同的是:里面的元素只有时间过期了之后才能取出来,头元素是已经过期的元素中最早过期的。如果没有过期的元素,则poll方法返回null.但是size方法返回的所有的元素,包括过期的和没有过期的。
注意:该集合的iterator并不保证访问顺序,你不能迭代是按时间顺序的。
元素过期的定义:元素的getDelay方法返回一个小于或等于0的值。
注意:该容器中的元素必须实现Delayed接口


主要方法分析

1.主要的成员变量

    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 q来进行元素的排序,用ReentrantLock 作为主锁,用Condition 来形成队列。
2 添加元素方法 offer,add、put直接调用的offer

public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            if (q.peek() == e) 
            {//如果当前堆上的头元素变了,则leader无效,所有的元素强leader
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

3 删除元素take方法,阻塞版本

 /*过程
     a:获取主锁
     b:如果队列为空,没有元素,则等待在Condition上,醒来重试(执行a)
     c:如果当前队列头元素到期了,就直接可以取走了,跳到g.
         (这里不是先判断是不是有leader?说明leader的优先级并不是绝的的)

     d:如果没有到期,且有leader,就不关我什么事了,因为当前元素到期了,也是leader取走了
     f:如果还没有leader,那我就是leader吧,就等定长时间(当前的第一个元素到期时间)
     e:如果醒过来时,我还是leader(说明中途没有插入更容易过期的元素),那我就将leader置空,然后跳到b
                 在下一轮中取走

     g:如果当前leader为null,且队列不为空,则唤醒一个等待线程
     h:释放锁
     */
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) 
            {
                E first = q.peek();
                if (first == null)
                    available.await();
                else {
                    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 {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

结合offer和take看leader:offer时,如果堆上的元素不变,则是不会调用condition.signal()的。
从一个例子来看:

  1. queue为空。A1线程首先进来take,leader=null。A1发现队列为空,则在conditon上等待。
  2. A2进来,同样,等待。
  3. B进来offer一个元素,过期时间为3个时间单位后,此时新元素变成首位置,调用condition.signal,激活A1或是A2。
  4. 假如A1获取锁,走一遍流程,此时首元素没有过期,leader=null,则设置自己为leader,然后睡眠3个时间单位,醒来后重新获取锁后发现自己还是leader,那我就获取元素了(这个时候不可能别的线程进入抢了,因为我把主锁拿到了,且队列上有元素到期了),在下次循环时,直接就取元素了。(如果没有新的元素插入,则该线程会一直保持leader位置)
  5. 情况二:如果线程A1在等待1时间单位就被中断了(有新的在一个时间单位内过期的元素插入了,重置了leader=null),但是这次A1并没有抢到锁,而是A2抢到锁了,则A2抢到后,A2发现当前元素还没有过期,且leade为null,则将自己设为leader,然后等待一个时间单位。如果等待一个时间单位后,同时如果有新的线程A3进来了,A3抢到了锁,发现队顶元素到期了,则直接取走了,然后直接释放了锁(因为leader!=null,所以A3不会唤醒队列中的其他线程)。在A3释放锁后,A2获取锁成功,发现leader还是自己,然后重新探测队列,发现队列头第一个元素还剩一个时间单位才过期,则又陷入等待。。。(队顶元素已经变了,变成了之间的第一个插入的元素,队顶元素不稳定,也就是为什么线程线程在等待时,要释放队顶元素引用的原因,不然导致原先的那个元素一直得不到清理)。在等待1个时间单位后,如果没有其他新的线程进入或是新的线程没有获取到锁,A2又重新获取了锁,发现leader是自己(则下一次就能取走元素了,自己的leader位置就可以空出来了),则在下一次中取走元素,然后激活等待中的一个线程,释放锁。

从上面分析可以看出:
1.leader位置在没有新的更容易过期的元素插入的时候,是稳定的。此时leader线程比之前的线程优先级更高。
2.leader线程如果处于睡眠状态,在醒来后,不一定比新线程的优先级高,这时,他们会抢夺主锁,获得主锁的线程取走队列顶层元素。
3.如果leader抢夺失败,则新线程不会唤醒condition中的线程,而是leader又重新抢夺锁。
4.condition中的线程会发生饥饿现象。如队列中没有新的更容易的过期的元素插入,且不断的有新线程获取的锁,则condition中都不会有唤醒的机会。
5.leader在等待队列头元素过期时,释放了当前的头元素的引用,因为头元素是不稳定的,且下一次leader并不能保证能获取锁,从该方法中返回,这样就会导致该元素存活时间太长。


总结

1.不允许null元素
2.容器中的里面的元素必须实现了 Delayed接口
3.取元素时,元素的要求是已经过期了的,如果没有到期的,则阻塞版本的方法会阻塞。
4.也提供了阻塞版本的方法:如果当前队列为空,或是没有元素到期,则返回null.
5.目前还没有想到这个容器的用途,但是之前看Redis时,有看到类似的东西。用于看过期键,如果键过期了则删除该键,减少内容的损耗。可能这这种集合也是用来维护缓存的吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值