使用Java的DelayQueue容易碰到的一个坑

本文深入探讨了java.util.concurrent.DelayQueue的使用方法,并通过一个具体示例解析了其内部工作机制。特别关注了getDelay方法中时间单位转换的问题及其对CPU占用的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天不忙,学习一下java.util.concurrent.DelayQueue这个类的使用。参照了
[url]http://www.concretepage.com/java/example_delayqueue_java.php[/url]
上的例子,但是这里有个坑。

先看一下整个code吧:


import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;


public class DelayQueueExample {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
DelayQueue<DelayedElement> dq=new DelayQueue<DelayedElement>();
long now = System.currentTimeMillis();
System.out.println("current time in ms: " + now);
DelayedElement ob1=new DelayedElement("e1", now + 1000);
DelayedElement ob2=new DelayedElement("e2", now + 5000);
DelayedElement ob3=new DelayedElement("e3", now + 1500);

dq.add(ob1);
dq.add(ob2);
dq.add(ob3);

try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException( e );
}


while(dq.size() > 0){
try {
DelayedElement e = dq.take();
System.out.println("current time in ms: " + System.currentTimeMillis() + ", element:" + e.name);

} catch (InterruptedException e) {
throw new RuntimeException( e );
}

}
}

static class DelayedElement implements Delayed {
public long time;
public String name;
public DelayedElement(String name, long time){
this.name = name;
this.time = time;
}
@Override
public int compareTo(Delayed o) {
// TODO Auto-generated method stub
if(this.time < ((DelayedElement)o).time) return -1;
else if(this.time > ((DelayedElement)o).time)return 1;
else return 0;
}

@Override
public long getDelay(TimeUnit unit) {
// TODO Auto-generated method stub
long r = unit.convert(time - System.currentTimeMillis(), TimeUnit.NANOSECONDS);
//System.out.println("delay:" + r);
return r;
}

}
}


注意getDelay(TimeUnit unit),这个是实现Delayed这个接口时候必须要实现的一个方法,容易遇坑的地方就是

long r =  unit.convert(time - System.currentTimeMillis(), TimeUnit.NANOSECONDS);


这里应该使用的TimeUnit.MILLISECONDS而不是TimeUnit.NANOSECONDS,但是你会发现不管你用哪一个(或者其他的TimeUnit),这个程序的正确性是ok的,都会delay你所要的时间,例如分别使用这两种TimeUnit的输出:

If using MILLISECONDS:

current time in ms: 1369644922697
current time in ms: 1369644923697, element:e1
current time in ms: 1369644924197, element:e3
current time in ms: 1369644927697, element:e2
If using NANOSECONDS:

current time in ms: 1369645748910
current time in ms: 1369645749910, element:e1
current time in ms: 1369645750410, element:e3
current time in ms: 1369645753910, element:e2

那么会有什么问题呢?


Retrieves and removes the head of this queue, waiting if necessary until an element with an expired delay is available on this queue.
Returns:
the head of this queue
Throws:
java.lang.InterruptedException
181
182 public E take() throws InterruptedException {
183 final ReentrantLock lock = this.lock;
184 lock.lockInterruptibly();
185 try {
186 for (;;) {
187 E first = q.peek();
188 if (first == null) {
189 available.await();
190 } else {
191 long delay = first.getDelay(TimeUnit.NANOSECONDS);
192 if (delay > 0) {
193 long tl = available.awaitNanos(delay);
194 } else {
195 E x = q.poll();
196 assert x != null;
197 if (q.size() != 0)
198 available.signalAll(); // wake up other takers
199 return x;
200
201 }
202 }
203 }
204 } finally {
205 lock.unlock();
206 }
207 }



看一下DelayQueue的take()方法会发现,在队列首对象的delay>0的时候,等待的时间单位是nanos(193行),所以如果我刚才getDelay()里面没有将ms转换成ns,那么数值会小很多,line 193 会很快执行,再次循环进行判断,delay仍然大于0,注意,总的等待时间是固定的,现在是每次wait的时间片变小了,所以循环的次数多了,造成一个结果就是cpu占用上升!

如果打印出每次delay的值便可以看到用nanos多做了多少次循环,读者可以自己看一下,呵呵。

遇到这个坑的人我google到有:

[url]http://www.blogjava.net/killme2008/archive/2010/10/22/335897.html[/url]
Java中的DelayQueue一个基于优先级队列实现的延迟队列。它可以用于定时任务调度、缓存过期等场景。 DelayQueue中的元素必须实现Delayed接口,该接口继承自Comparable接口,因此元素需要实现compareTo方法,以便在队列中维护元素的优先级。 DelayQueue中的元素按照延迟时间的大小进行排序,即延迟时间短的元素排在队列的前面。当从队列中取出元素时,只有延迟时间到了的元素才会被取出。 以下是一个使用DelayQueue的简单示例: ```java import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class DelayQueueDemo { public static void main(String[] args) throws InterruptedException { DelayQueue<DelayedElement> queue = new DelayQueue<>(); queue.add(new DelayedElement("task1", 3000)); // 延迟3秒执行 queue.add(new DelayedElement("task2", 2000)); // 延迟2秒执行 queue.add(new DelayedElement("task3", 1000)); // 延迟1秒执行 while (!queue.isEmpty()) { DelayedElement element = queue.take(); // 取出元素 System.out.println(System.currentTimeMillis() + ": " + element); } } } class DelayedElement implements Delayed { private String name; private long expireTime; public DelayedElement(String name, long delay) { this.name = name; this.expireTime = System.currentTimeMillis() + delay; } @Override public long getDelay(TimeUnit unit) { return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS)); } @Override public String toString() { return "DelayedElement{" + "name='" + name + '\'' + ", expireTime=" + expireTime + '}'; } } ``` 在上面的示例中,我们创建了一个DelayQueue对象,并向其中添加了三个DelayedElement元素,分别表示3秒、2秒和1秒后执行的任务。然后在一个循环中不断取出元素,直到队列为空。由于每个元素的延迟时间不同,因此取出的顺序也是不同的。 以上就是JavaDelayQueue的简单使用方法。需要注意的是,DelayQueue是线程安全的,可以在多线程环境下使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值