System.currentTimeMillis()的慎用

轉自:http://blog.sina.com.cn/s/blog_6b8bd9d80101fe8t.html

在對新寫的超快xml解析器和xpath引擎進行效率測試時,爲獲取執行時間,開始也沒多想就用了System.currentTimeMillis() 來做的。由此碰到一個極其詭異的問題,同樣的代碼循環執行數次,分析每一次的執行時間,發現一大部分執行時間爲小於1毫秒,但其間也發現有相當一部分的執行時間有非常大的跳躍,而且時間都近似16毫秒左右。這個1毫秒和16毫秒結果,以計算機的運行速度看,差距是驚人的,必須找出其原因。

根據經驗,在16毫秒時間內,cpu可以運算的指令數量是相當驚人的,所以可以基本斷定這16ms的差距,應當不是cpu在執行指令,另外因爲測試過程中gc輸出也已經打開,未見gc發生,所以懷疑可能是發生了什麼io阻塞,比如文件讀寫、加載類庫、或者什麼網絡操作等,由於筆者測試的系統的環境比較複雜,其間有用到ehCache,數據庫操作等,排查起來非常不容易。

 

 

在困擾了好一陣之後,忽然想到可能計時系統有誤差,這才翻回來查了下System.currentTimeMillis() 的文檔,原來這個方法調用了個native方法,獲取的時間精度會依賴於操作系統的實現機制。奶奶的!

 

 

既然不準,就看看有沒更準的方法,在jdk5源碼中,挨着System.currentTimeMillis() 定義就是System.nanoTime() 方法,靠,一下來了個精準1000000倍的取納秒的方法,不過看jdk文檔介紹,這個方法的精度依然依賴操作系統,不過再不準也能達到微秒級的準確度,放心用吧!結果測試結果一下準確了不少,沒再發現比較大的跳躍了。

 

 

所以這裏提醒做非常精確的時間統計的朋友,謹慎使用System.currentTimeMillis() 。

 

 

 

雖然用取納秒的方法解決了我的問題,但對於爲何使用System.currentTimeMillis() 會造成那麼大的跳躍,一直無解,有點鬱悶。幸運的是今天恰巧看到一個網友做的測試很有意思,用事實數據證明了這個跳躍的存在,分享給感興趣的同學!

 

(原文鏈接:http://blog.csdn.net/elky1982/archive/2009/10/16/4677365.aspx)

 

以下內容爲轉帖:

 

 

在Java中可以通過System.currentTimeMillis()或者System.nanoTime() (JDK>=5.0) 方法獲得當前的時間的精確值。但是通過閱讀Javadoc,我們發現這兩個方法並不一定保證得到你所期望的精度。先來看System.currentTimeMillis():

 

Returns the current time in milliseconds. Note that while the unit of time of the return value is a millisecond, the granularity of the value depends on the underlying operating system and may be larger. For example, many operating systems measure time in units of tens of milliseconds.

 

誠如上面所說返回值的粒度依賴於底層操作系統,那麼它在不同的平臺上到底能提供是麼樣的精度,是否像函數名所寫的那樣真正 精確到1毫秒呢?看下面一段測試程序:

 

 

 

public class ClockAccuracyTest {

 

public static void main(String args[]) {

 

SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss:SSS");

int size = 4000000;

 

// create an array to hold millisecond times

// and loop to capture them

long times[] = new long[size];

for (int i = 0; i < size; i++) {

times[i] = System.currentTimeMillis();

}

 

// now display the unique times

long time = times[0];

long previousTime = times[0];

long count = 0;

Set deltas = new HashSet();

long delta = 0;

long minDelta = Long.MAX_VALUE;

long maxDelta = Long.MIN_VALUE;

for (int i = 0; i < size; i++) {

if (times[i] > time) {

delta = time - previousTime;

deltas.add(delta);

if (delta > 0 && delta < minDelta) {

minDelta = delta;

} else if (delta > maxDelta) {

maxDelta = delta;

}

 

System.out.print("raw=");

System.out.print(time);

System.out.print(" | formatted=");

System.out.print(formatter.format(new Date(time)));

System.out.print(" | frequency=");

System.out.print(count);

System.out.print(" | delta=");

System.out.print(delta);

System.out.println("ms");

 

previousTime = time;

time = times[i];

count = 0;

} else {

count++;

}

}

 

System.out.println("");

System.out.println("Minimum delta : " + minDelta + "ms");

System.out.println("Maximum delta : " + maxDelta + "ms");

 

}

 

}

 

 

 

這段程序循環調用 System.currentTimeMillis()方法, 記錄並顯示結果,在我機器(Windows XP SP3)上運行輸出如下:

 

 

raw=1255659457187 | formatted=16-十月-2009 10:17:37:187 | frequency=147250 | delta=0ms

raw=1255659457203 | formatted=16-十月-2009 10:17:37:203 | frequency=447674 | delta=16ms

raw=1255659457218 | formatted=16-十月-2009 10:17:37:218 | frequency=436583 | delta=15ms

raw=1255659457234 | formatted=16-十月-2009 10:17:37:234 | frequency=439379 | delta=16ms

raw=1255659457250 | formatted=16-十月-2009 10:17:37:250 | frequency=426547 | delta=16ms

raw=1255659457265 | formatted=16-十月-2009 10:17:37:265 | frequency=447048 | delta=15ms

raw=1255659457281 | formatted=16-十月-2009 10:17:37:281 | frequency=459522 | delta=16ms

raw=1255659457296 | formatted=16-十月-2009 10:17:37:296 | frequency=414816 | delta=15ms

raw=1255659457312 | formatted=16-十月-2009 10:17:37:312 | frequency=458826 | delta=16ms

 

Minimum delta : 15ms

Maximum delta : 16ms

 

輸出的四列從左到右分別是原始的毫秒值、格式化的時間、每個值循環的次數、與上一個不同值的差。可以看到在Windows上 System.currentTimeMillis()方法並不能提供1ms的計時粒度,它的粒度爲15~16ms,從網上的其它文章來看,這個結果基本上是一致的,這也驗證了Javadoc上所寫的“ the granularity of the value depends on the underlying operating system and may be larger ”。在其他操作系統,如Linux、Solaris上,我沒有進行測試,但從網上的一些測試結果看, currentTimeMillis方法在某些操作系統能夠提供精確的1毫秒計時粒度。這是不是意味着Java在Windows上無法進行精確的毫秒計時了呢?當然不是,一種解決方案是採用JNI調用,利用Windows系統提供的函數計時;還有一個更簡便的辦法,就是使用JDK5.0加入的System.nanoTime()方法。Javadoc對該方法的描述如下:

 

Returns the current value of the most precise available system timer, in nanoseconds.

 

This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time. The value returned represents nanoseconds since some fixed but arbitrary time (perhaps in the future, so values may be negative). This method provides nanosecond precision, but not necessarily nanosecond accuracy. No guarantees are made about how frequently values change. Differences in successive calls that span greater than approximately 292 years (263 nanoseconds) will not accurately compute elapsed time due to numerical overflow.

 

它返回系統能夠提供的最爲精確的計時,以納秒(10億分之一秒)爲單位,但並不保證納秒級精度。把上面的測試程序中黑體的一行改爲“times[i] = System.nanoTime();”,並把所有的“ms”該成“ns”,循環次數改爲10。運行輸出如下:

 

raw=8705892679376 | formatted=17-十一月-2245 23:31:19:376 | frequency=1 | delta=0ns

raw=8705892681053 | formatted=17-十一月-2245 23:31:21:053 | frequency=0 | delta=1677ns

raw=8705892682449 | formatted=17-十一月-2245 23:31:22:449 | frequency=0 | delta=1396ns

raw=8705892683846 | formatted=17-十一月-2245 23:31:23:846 | frequency=0 | delta=1397ns

raw=8705892685522 | formatted=17-十一月-2245 23:31:25:522 | frequency=0 | delta=1676ns

raw=8705892686919 | formatted=17-十一月-2245 23:31:26:919 | frequency=0 | delta=1397ns

raw=8705892688316 | formatted=17-十一月-2245 23:31:28:316 | frequency=0 | delta=1397ns

raw=8705892689713 | formatted=17-十一月-2245 23:31:29:713 | frequency=0 | delta=1397ns

raw=8705892691110 | formatted=17-十一月-2245 23:31:31:110 | frequency=0 | delta=1397ns

 

Minimum delta : 1396ns

Maximum delta : 1676ns

 

我們看到frequency=0,這意味着每執行一次循環,nanoTime方法的返回值都發生了改變,改變的差值平均大約爲1467ns(不同性能的機器結果會不同)。這說明nanoTime方法計時的精度是非常高的,粒度比方法本身的調用執行耗時還要小。

 

回到上面的問題,如何在windows上實現精確的毫秒計時呢。答案就是用“System.nanoTime()/1000000L”代替“System.currentTimeInMills()”。把測試程序黑體的一行代碼改爲"times[i] = System.nanoTime()/1000000L;",循環次數5000,運行輸出結果如下:

 

raw=9487129 | formatted=01-一月-1970 10:38:07:129 | frequency=202 | delta=0ms

raw=9487130 | formatted=01-一月-1970 10:38:07:130 | frequency=704 | delta=1ms

raw=9487131 | formatted=01-一月-1970 10:38:07:131 | frequency=621 | delta=1ms

raw=9487132 | formatted=01-一月-1970 10:38:07:132 | frequency=618 | delta=1ms

raw=9487133 | formatted=01-一月-1970 10:38:07:133 | frequency=696 | delta=1ms

raw=9487134 | formatted=01-一月-1970 10:38:07:134 | frequency=695 | delta=1ms

raw=9487135 | formatted=01-一月-1970 10:38:07:135 | frequency=698 | delta=1ms

raw=9487136 | formatted=01-一月-1970 10:38:07:136 | frequency=698 | delta=1ms

 

Minimum delta : 1ms

Maximum delta : 1ms

 

我們看到粒度delta變爲了1ms。循環次數frequency平均爲676,也就是說每個循環運運行耗時1/676=0.001479ms=1479ns,與上一個測試結果的1467ns吻合。

 

結論:如果你的Java程序需要高精度的計時,如1毫秒或者更小,使用System.nanoTime()方法,它完全可以滿足你的需求。如果不行的話,建議你換臺更快的機器試試:)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章