相关文章:
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()的。
从一个例子来看:
- queue为空。A1线程首先进来take,leader=null。A1发现队列为空,则在conditon上等待。
- A2进来,同样,等待。
- B进来offer一个元素,过期时间为3个时间单位后,此时新元素变成首位置,调用condition.signal,激活A1或是A2。
- 假如A1获取锁,走一遍流程,此时首元素没有过期,leader=null,则设置自己为leader,然后睡眠3个时间单位,醒来后重新获取锁后发现自己还是leader,那我就获取元素了(这个时候不可能别的线程进入抢了,因为我把主锁拿到了,且队列上有元素到期了),在下次循环时,直接就取元素了。(如果没有新的元素插入,则该线程会一直保持leader位置)
- 情况二:如果线程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时,有看到类似的东西。用于看过期键,如果键过期了则删除该键,减少内容的损耗。可能这这种集合也是用来维护缓存的吧!