DelayQueue是一个无界阻塞的队列,队列中的每个元素都有一个过期时间,当要从队列中取出数据时,只有过期元素才会出队。
DelayQueue内部使用PriorityQueue存放元素,又用ReentrantLock实现线程同步。因为DelayQueue内部要获取元素的剩余时间,所以我们的数据类需要继承Delayed接口,Delayed又继承Comparable接口,实现排序,而自身只有一个getDelay()方法,用来获取元素的剩余时间,如果getDelay()返回<=0的值,则表示这个元素过期,通过take()方法即可取出他,如果没有过期值,则take()会一直阻塞。
DelayQueue类涉及到TimeUnit,是在java.util.concurrent包下的一个枚举类,可以非常方便实现时间单位的转化。
//秒转分
System.out.println(TimeUnit.SECONDS.toMinutes(2400));
//分转小时
System.out.println(TimeUnit.MINUTES.toHours(120));
//分转小时
System.out.println(TimeUnit.HOURS.convert(120,TimeUnit.MINUTES));
40
2
2
还可以让线程sleep
TimeUnit.MINUTES.sleep(1);
以上就是TimeUnit的一些用法。
在看DelayQueue的简单示例。首先定义数据类,MyData被实例化后,过期时间是当前时间+转换成毫秒的seconds。在getDelay中返回剩余时间(剩余时间=到期时间-当前时间)。toString()只是方便查看从实例化后到被取出打印花了多长时间。
public class MyData implements Delayed {
private long expire;
private int id;
private long start;
public MyData(int seconds) {
this.expire = System.currentTimeMillis()+TimeUnit.SECONDS.toMillis(seconds);
this.start=System.currentTimeMillis();
this.id = seconds;
}
@Override
public long getDelay(TimeUnit unit) {
long convert = unit.convert(expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
return convert;
}
@Override
public int compareTo(Delayed o) {
return (int)(this.getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() {
return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()-this.start) +"秒";
}
}
测试类,这里也就是入队5个元素,每个元素从被new出后开始算,当前时间+i秒后相继会过期。
public static void main(String[] args) throws IOException, InterruptedException { DelayQueue<MyData> queue =new DelayQueue<>(); for (int i = 1; i <=5 ; i++) { queue.offer(new MyData(i)); } for (;;){ System.out.println(queue.take()); } }
输出结果,也正是想要的。
1秒 2秒 3秒 4秒 5秒
offer()
这个方法用来将元素添加到队列,如果元素为null,则抛异常,q是PriorityQueue队列,PriorityQueue会更具自然顺序或Comparator接口进行排序。也就是说下面这段,q.peek方法并不一定是当前添加的元素,如果是当前添加元素,则重置leader线程为null,然后激活avaliable变量条件队列的一个线程,告诉他队列里面有元素了。
public boolean offer(E e) { //获取到独占锁 final ReentrantLock lock = this.lock; lock.lock(); try { //向PriorityQueue队列添加数据 q.offer(e); if (q.peek() == e) { leader = null; available.signal(); } return true; } finally { lock.unlock(); } }
take()
获取并移除队列中过期元素,没有则等待。
首先也是获取独占锁,然后使用peek获取一个元素(peek不移除元素)。
如果first为null,则把当前线程放入available中阻塞等待(available是Condition)。
如果first不为null,则调用这个元素的getDelay获取还有多长时间要过期(参数是NANOSECONDS,纳秒),如果<=0,则表示已经过期,直接出队返回,否则查看leader是否为null。
leader不为null则说明其他线程也在执行take(),则把线程放入条件队列。
如果leader为null,则让leader为当前线程,然后执行等待,剩余过期时间到达后,然后重置leader线程为null,重新进入循环,重新进入后就可以发现队列中的头部元素已经过期,然后返回他。
在最后的finally块中,如果判断结果为true,则说明当前线程从队列移除过期元素后,又有其他线程执行了入队,就会调用条件变量的signal方法,激活条件队列里面的等待线程。
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { //获取元素 E first = q.peek(); if (first == null) //等待 available.await(); else { // 获取first元素要过期的时间。 long delay = first.getDelay(NANOSECONDS); //如果小于等于0,则直接返回 if (delay <= 0) return q.poll(); first = null; if (leader != null) available.await(); else { //设置leader为当前线程。 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(); } }
poll()
获取并移除队头过去元素,如果没有过期元素则返回null。
较简单,只有一个if判断队列是否为空,不为空的话如果队头元素没有过期则返回null。
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { E first = q.peek(); if (first == null || first.getDelay(NANOSECONDS) > 0) return null; else return q.poll(); } finally { lock.unlock(); } }
size()
返回队列元素个数