java getdelay_java中DelayQueue的一个使用陷阱分析
最近工作中有接触到DelayQueue,网上搜索资料的时候发现一篇文章谈到DelayQueue的坑。点击打开链接
文中已经总结了遇到坑的地方,还有解决方案。不过我第一眼看一下没弄明白为什么,所以翻了翻源码深究了一下,下面把这个坑的原因以及原理分析一下。
首先是DelayQueue的take()方法:
1 public E take() throwsInterruptedException {2 final ReentrantLock lock = this.lock;3 lock.lockInterruptibly();4 try{5 for(;;) {6 E first =q.peek();7 if (first == null)8 available.await();9 else{10 long delay = first.getDelay(NANOSECONDS); //1
11 if (delay <= 0)12 returnq.poll();13 first = null; //don't retain ref while waiting
14 if (leader != null)15 available.await();16 else{17 Thread thisThread =Thread.currentThread();18 leader =thisThread;19 try{20 available.awaitNanos(delay); //2
21 } finally{22 if (leader ==thisThread)23 leader = null;24 }25 }26 }27 }28 } finally{29 if (leader == null && q.peek() != null)30 available.signal();31 lock.unlock();32 }33 }
首先看到注释2,这是一个带时间的await方法,时间单位是纳秒,传入的参数delay是从注释1通过调用first对象的getDelay方法获取的。first对象是E类型的,E是一个实现了Delayed接口的泛型。
这里看看接口Delayed的源码:
1 public interface Delayed extends Comparable{2
3 /**
4 * Returns the remaining delay associated with this object, in the5 * given time unit.6 *7 *@paramunit the time unit8 *@returnthe remaining delay; zero or negative values indicate9 * that the delay has already elapsed10 */
11 longgetDelay(TimeUnit unit);12 }
就只有一个getDelay(TimeUnit)方法,它返回的指定的TimeUnit的时间长度。显然,具体的实现类要实现该方法才行。
那么来看一下具体的getDelay(TimeUnit)方法的实现吧,我看了几篇文章,基本上大同小异,都是如下这般实现的:
1 public longgetDelay(TimeUnit unit) {2 return unit.convert(this.expire -System.currentTimeMillis() , TimeUnit.MILLISECONDS);3 }
原博主很贴心的提醒了,这个地方convert方法的第二个参数,应该要使用TimeUnit.MILLISECONDS而不是TimeUnit.NANOSECONDS(虽然不管使用什么时间单位都不会导致程序出现错误的结果,但是用错了时间单位的话,CPU可就遭殃了)。那么为什么会一定要强调要使用MILLISECONDS这个单位呢?
继续看看convert方法的源码吧,在TimeUnit枚举类中,定义了若干时间单位,他们有各自的convert方法的实现,先来看看TimeUnit.NANOSECONDS的:
1 NANOSECONDS {2 public long toNanos(long d) { returnd; }3 public long toMicros(long d) { return d/(C1/C0); }4 public long toMillis(long d) { return d/(C2/C0); }5 public long toSeconds(long d) { return d/(C3/C0); }6 public long toMinutes(long d) { return d/(C4/C0); }7 public long toHours(long d) { return d/(C5/C0); }8 public long toDays(long d) { return d/(C6/C0); }9 public long convert(long d, TimeUnit u) { returnu.toNanos(d); }10 int excessNanos(long d, long m) { return (int)(d - (m*C2)); }11 },
可以看到,convert方法又直接调用了TimeUnit.toNanos方法,直接就把第一个参数d当做一个纳秒的时间长度给返回了。
同理看看TimeUnit.MILLISECONDS定义的方法:
1 MILLISECONDS {2 public long toNanos(long d) { return x(d, C2/C0, MAX/(C2/C0)); } //static final long C0 = 1L; static final long C1 = C0 * 1000L;static final long C2 = C1 * 1000L;
3 public long toMicros(long d) { return x(d, C2/C1, MAX/(C2/C1)); }4 public long toMillis(long d) { returnd; }5 public long toSeconds(long d) { return d/(C3/C2); }6 public long toMinutes(long d) { return d/(C4/C2); }7 public long toHours(long d) { return d/(C5/C2); }8 public long toDays(long d) { return d/(C6/C2); }9 public long convert(long d, TimeUnit u) { returnu.toMillis(d); }10 int excessNanos(long d, long m) { return 0; }11 },
回到我们的实际使用场景,take方法中long delay = first.getDelay(NANOSECONDS); -> NANOSECONDS.convert(long d, TimeUnit u) -> u.toNanos(d)。如果我们在getDelay方法实现中,convert方法第二个参数传入的是NANOSECONDS,那么就直接返回d;如果convert方法第二个参数传入的是MILLISECONDS,那么返回就是MILLISECONDS.toNanos(d),得到的结果就是1000*1000*d。
可以发现,convert方法的第二个参数TimeUnit,实际上是跟着第一个参数d的时间单位走的。如果实现时候直接使用time - System.currentTimeMillis()作为第一个参数,实际上它的时间单位确实应该是MILLISECONDS,那么如果第二个参数传错了为NANOSECONDS,那就导致take方法中的awaitNanos方法等待时间缩短了1000*1000倍,这样带来的cpu空转压力是巨大的。
分析了这么多,其实看看jdk中TimeUnit类对convert方法的注释,很容易就理解了:
/**
* Converts the given time duration in the given unit to this unit.
* Conversions from finer to coarser granularities truncate, so
* lose precision. For example, converting {@code 999} milliseconds
* to seconds results in {@code 0}. Conversions from coarser to
* finer granularities with arguments that would numerically
* overflow saturate to {@code Long.MIN_VALUE} if negative or
* {@code Long.MAX_VALUE} if positive.
*
*
For example, to convert 10 minutes to milliseconds, use:
* {@code TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MINUTES)}
*
* @param sourceDuration the time duration in the given {@code sourceUnit}
* @param sourceUnit the unit of the {@code sourceDuration} argument
* @return the converted duration in this unit,
* or {@code Long.MIN_VALUE} if conversion would negatively
* overflow, or {@code Long.MAX_VALUE} if it would positively overflow.
*/
public long convert(long sourceDuration, TimeUnit sourceUnit) {
throw new AbstractMethodError();
}
这里很明确的指出了,convert方法的第二个参数sourceUnit(@param sourceUnit the unit of the {@code sourceDuration} argument)应该是第一个参数sourceDuration的时间单位。会产生原链接中提到的那样的错误使用,应该就是理解错了这个convert方法参数的含义,以为第二个参数的时间单位是要转换到的时间单位。
不过这个陷阱确实有点绕,在getDelay(TimeUnit unit)方法里面,调用unit.convert(long sourceDuration, TimeUnit sourceUnit)方法,一下出来了两个TimeUnit变量,不仔细一点的话真是容易被坑啊。当然,要是自身的getDelay方法实现不用unit.convert方法或许就避免了该问题了。
java getdelay_java中DelayQueue的一个使用陷阱分析相关推荐
- 如何在java面试中给出一个出彩的自我介绍
自我介绍是java面试中一个必不可少的环节,一个出彩的自我介绍,可以给面试官留下好的印象,帮助你拿下心仪的offer,但是很多初级程序员都不知道如何去做自我介绍, 甚至有些工作了很多年的程序员面试时的 ...
- 道听途说——JAVA文件中只能含有一个Public类
java程序是从一个public类的main函数开始执行的 就像C程序是从main()函数开始执行一样. 只能有一个public类是为了给类装载器提供方便. 一个public类只能定义在以它的类名为文 ...
- Java泛型中? 和 ? extends Object的异同分析
点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 刘一手 来源 | 公众号「锅外的大佬」 Jav ...
- Java 8中Collectors.toMap空指针异常源码分析
当需要将一个List转换为Map时,可以使用 Java 8 中的 Collectors.toMap() 方法,Map是由key-value组成的键值对集合,在使用Collectors.toMap() ...
- 为什么一个java源文件中只能有一个public类
多个public类 可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致.一个文件中可以只有非public类,如果只有一个非public类,此类可以跟文件名不同. 原因 ...
- java 线程的回收,Java语言中提供了一个▁线程,自动回收动态分配的内存。
必不稳定对于地占的可少一个产品长期场是领市,中提自动的产品才含量科技一个有高能拥企业,含量科技而这种高,的资只有足够具备金投技术入和人才. 下列呼吸困难中类型,▁线心功由左能是能衰最可致的竭所是. 2 ...
- 在java代码中歘创建一个随机的字符串
String uuid = UUID.randomUUID().toString(); // uuid 在 java.util包中 System.out.println(u ...
- android 双线程等待,在Java/Android中启动另一个线程之前如何等待线程完成?
在回答您的问题之前,我强烈建议您查看 ExecutorServices,例如 ThreadPoolExecutor. 现在回答你的问题: 如果要等待上一个线程完成,在开始下一步之前,您可以在之间添加t ...
- java 调用jar_在Java程序中执行另一个jar
希望这可以帮助:public class JarExecutor {private BufferedReader error;private BufferedReader op;private int ...
最新文章
- 微信服务号、公众号、企业号注册
- Redis集群——利用Gearman在Lnmp架构中做MySQL的缓存服务器
- Learning by doing 系列文章(之一)如何在 Python 中使用 epoll ?
- mysql emma 使用教材_emma的使用
- win10下右键菜单添加“打开cmd”
- 《笑傲网湖》第五回 状态检测防火墙
- Effective C# 学习笔记(八)多用query语法,少用循环
- 目标指令c语言是什么,什么是C中的目标文件?
- H - Maximal submatrix HDU - 6957
- pip临时使用国内下载源,提高下载的速度
- 做自媒体和有没有文化没有太大关系
- 【专题】CSDN下载频道4月热门资源top100汇总
- ECharts实操手册
- 关于redis客户端连接不上
- cmake简洁教程 - 第五篇
- arduino笔记41:直流电机 + 步进电机参数、原理
- OSError: exception: access violation writing,
- 软件测试的14种类型
- 官宣了!大杀四方的 Master 就是阿尔法狗
- 【英语面试】七.计算机研究生面试自我介绍范文5篇(英文)
热门文章
- python传中文参数_解决Python传递中文参数的问题
- java重定向cookie_response请求转发和重定向,cookie
- mysql简单语句_MySQL 简单的语句
- boot定时任务开启和关闭 spring_Spring之定时任务实践
- Win7旗舰版系统网页显示不全怎么办
- Win10系统省电模式的设置教程
- 开发个好的RTMP播放器到底难在哪里?RTMP播放器对标和考察指标
- Java包装类中的equals方法
- 北交大计算机学院复试经验,2014考研复试:过来人考研复试经验谈-北交大计算机系...
- hive UDF函数取最新分区