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的一个使用陷阱分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 常用编译参数,php编译参数,不
- 下一篇: mysql with as 用法_Pyt