緣起
我們的代碼里用到了Timer,差不多是這樣:
Timer timer = new Timer("xxx");
timer.schedule(task, 0, 20);
每20ms執行一次task,這里的task并不是耗時操作,基本在1ms左右完成,但是在某些6.x設備上的調度結果顯示,差不多是60ms才執行一次,和指定的20ms相差很遠啊!!!這么神奇的現象于我來說怎么會放過呢,于是乎點進去研究了發源碼。下面主要說下幾個關鍵的結論,供參考。
結論1
每個Timer背后對應一個線程,n多個TimerTask都會在這個timer實例對應的線程中執行。其中只要有某一個task的run方法拋了異常,那么會導致整個timer的thread提前結束,后面所有的任務都不會被執行;坑!!!
結論2
Timer對調度的支持是基于絕對時間,即實現里用到了System.currentTimeMillis()
,而不是相對時間的,因此任務對系統時鐘的改變是敏感的,而ScheduledThreadPoolExecutor只支持相對時間;
結論3
重復執行的schedule方法是相對上次task執行完的時間,比如period是10s,現在時間是9:00:10,假設上一個task執行完是50s,那么得到9:01:00的時候,第2個task才會被調度;可以看出在fixed-delay模式下,下一個task什么時候被調度取決于上一個task什么時候執行完,這樣就造成了時效準確性問題;
* In fixed-delay execution, each execution is scheduled relative to
* the actual execution time of the previous execution. If an execution
* is delayed for any reason (such as garbage collection or other
* background activity), subsequent executions will be delayed as well.
結論4
重復執行的scheduleAtFixedRate方式是相對上次task開始執行的時間(被調度的時間),所以各個task開始執行的時間都是固定的,但因為所有task需要在同一個線程中執行,所以會存在比如某次task執行的時間比較久,導致當前時間超過了接下來2、3個task的調度時間,就會出現連續快速的調度現象,并不是你想象的間隔period,因為只有這樣才能確保整體平均下來確實是按照fixed-rated調度的;
結論5
在Java5或更高的版本中,幾乎沒有任何理由再使用Timer了,請使用ScheduledThreadPoolExecutor代替,參考《Java并發編程實踐6.2.5節》。