Java 排程器延時存在誤差,是否有更精確的排程方法?
首先,複習一把Java中的時間單位:
* 1毫秒=1ms=1/1000秒,表達方式TimeUnit。MILLISECONDS
* 1微秒=1μs=1/1000毫秒,表達方式TimeUnit。MICROSECONDS
* 1納秒=1ns=1/1000微秒,表達方式TimeUnit。NANOSECONDS
然後,我猜測你是由於時間單位直接使用毫秒,沒有使用最精確的納秒導致的,於是我寫了一個程式檢測:
public
class
TimeTest
{
static
final
long
PER_NANO
=
1000
*
1000
;
//每毫秒為1000*1000納秒
public
static
void
main
(
String
[]
args
)
{
final
ScheduledExecutorService
scheduler
=
Executors
。
newScheduledThreadPool
(
1
);
final
long
times
[]
=
new
long
[
100
];
//用來存放時間的陣列
Worker
worker
=
new
Worker
(
times
);
//重複任務,延遲100ms開始,間隔100ms
final
ScheduledFuture
<?>
beeperHandle
=
scheduler
。
scheduleAtFixedRate
(
worker
,
100
,
100
,
TimeUnit
。
MILLISECONDS
);
//延遲任務,延遲10000ms執行。取消重複任務,輸出結果
scheduler
。
schedule
(
new
Runnable
()
{
public
void
run
()
{
beeperHandle
。
cancel
(
true
);
long
max
=
0
;
for
(
int
i
=
1
;
i
<
times
。
length
;
i
++)
{
long
diff
=
times
[
i
]
-
times
[
i
-
1
];
max
=
Math
。
max
(
max
,
diff
);
System
。
out
。
println
(
diff
);
}
double
maxdiff
=
((
double
)
(
max
)
/
PER_NANO
)
-
100
;
System
。
out
。
println
(
“max = ”
+
maxdiff
+
“ ms”
);
scheduler
。
shutdown
();
}
},
10000
*
PER_NANO
,
TimeUnit
。
NANOSECONDS
);
}
//為了防止IO佔用時間,把時間先存入陣列,最後再一次輸出
static
class
Worker
implements
Runnable
{
private
static
int
number
;
//序號
private
final
long
times
[];
//時間陣列
Worker
(
long
times
[])
{
this
。
times
=
times
;
}
@Override
public
void
run
()
{
times
[
number
++]
=
System
。
nanoTime
();
}
}
}
多次執行結果如下(單位為納秒,省略了前面的輸出):
max = 0。9890390000000053 ms
max = 0。984603000000007 ms
max = 0。991429999999994 ms
我發現誤差沒有超過2ms的。
我修改了時間單位為納秒:
final
ScheduledFuture
<?>
beeperHandle
=
scheduler
。
scheduleAtFixedRate
(
worker
,
100
*
1000
*
1000
,
100
*
1000
*
1000
,
TimeUnit
。
NANOSECONDS
);
多次執行結果如下(單位為納秒,省略了前面的輸出):
max = 0。993819000000002 ms
max = 0。9982550000000003 ms
max = 7。663642999999993 ms
終於出現了一個7ms的。因此我感覺靠使用納秒為時間單位,並沒有什麼幫助。
更好的方法,還沒想到:(
Java不咋熟,不過看到10ms的誤差,這個明顯是作業系統排程的時間片,也就是系統那裡排程執行緒的時間精度是10ms。
Win下有個Win32APItimeBeginPeriod,可以把時間片設定成1ms。這樣誤差應該能控制在1ms左右
再有,如果定時的回撥邏輯能確保在觸發間隔內完成,就是自己起個執行緒不停輪詢時間。
最後,非實時作業系統,沒有很精確的定時器,尤其是現在的搶佔式多工,執行緒的執行延遲是未知的。
定時器的精度跟作業系統跟硬體有關係。linux支援高精度納秒級喚醒,windows只支援毫秒級喚醒,所以我猜測你用的是windows系統,你可以在linux上試試看,另外將執行緒的優先順序定的高一些,以便喚醒後能更快得到CPU資源。
換linux伺服器,我們線上跑的應用裡面的定時器十天都不會差1ms。
用java就別就糾結這10毫秒了,一個gc,全世界都安靜了。即便是非gc語言,還涉及到作業系統的排程