commons-lang的FastDateFormat是一個thread-safe的,對SimpleDateFormat的一個重新實現。
SimpleDateFormat為什么不是thread-safe的呢?看一下具體實現就知道了,其父類中定義了成員變量Calendar,每次格式化日期時都會先重置這個Calendar的時間:calendar.setTime(date),這樣多個線程不就出問題了。
而FastDateFormat是thread-safe的,但從其源代碼看解決方案也很直接,每次格式化時都new一個Calendar對象出來,每次咱都用新的不就行了。單從這方面看FastDateFormat還真沒Fast多少,但FastDateFormat比SimpleDateFormat還是先進了一點,對于同一種格式化日期的pattern,FastDateFormat可以保證只有一個實例產生,實現了對pattern的管理。
FastDateFormat的這種方式還是會帶來一些問題,比如從緩存中獲取pattern時在多個線程間的同步問題。從commons-lang2.6的源代碼中看到下面的方法:
public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale)
可見大并發下還是會有鎖等待的(當時為什么沒有使用Double-check方式呢?)。commons-lang3.3對這部分做了優化,使用了ConcurrentHashMap作為緩存。
下面是我做的一些測試,
測試程序:
Case1:使用FastDateFormat
public static long currentSystemTimeMillis() {
FastDateFormat fdf = FastDateFormat.getInstance("yyyyMMddHHmmss");
return Long.parseLong(fdf.format(System.currentTimeMillis()));
}
Case2:直接使用Calendar
public static long currentSystemTimeMillis() {
Calendar rightNow = Calendar.getInstance();
rightNow.setTime(new Date(System.currentTimeMillis()));
int year = rightNow.get(Calendar.YEAR);
int month = rightNow.get(Calendar.MONTH) + 1;
int day = rightNow.get(Calendar.DAY_OF_MONTH);
int hour = rightNow.get(Calendar.HOUR_OF_DAY);
int minute = rightNow.get(Calendar.MINUTE);
int second = rightNow.get(Calendar.SECOND);
String strDateTime =
year
+ (month < 10 ? "0" + month : month + "")
+ (day < 10 ? "0" + day : day + "")
+ (hour < 10 ? "0" + hour : hour + "")
+ (minute < 10 ? "0" + minute : minute + "")
+ (second < 10 ? "0" + second : second + "");
return Long.parseLong(strDateTime);
}
//測試主方法
public static void testDateFormat() throws Exception {
System.out.println("Begin test of currentSystemTimeMillis()");
System.out.println("currentSystemTimeMillis:"+currentSystemTimeMillis());
int tCnt = 50;
Thread[] threads = new Thread[tCnt];
for (int i = 0; i < tCnt; i++) {
Runnable run = new Runnable() {
public void run() {
try {
int runCounter = 0;
for (long i = 0; i < 100000l; i++) {
currentSystemTimeMillis();
runCounter++;
}
System.out.println(Thread.currentThread().getName()
+ " finished. runCounter="
+ runCounter);
} catch (Exception e) {
}
}
};
threads[i] = new Thread(run, "Thread" + i);
}
long start = System.currentTimeMillis();
for (int i = 0; i < tCnt; i++) {
threads[i].start();
}
for (int i = 0; i < tCnt; i++) {
threads[i].join();
}
System.out.println("Test ended cost:" + (System.currentTimeMillis() - start));
}
測試環境:
CPU: AMD Phenom II 4核 3.4GHz
RAM: 4GB
OS : WINDOWS 7
50個線程,每個線程調用10萬次
測試結果(程序總共執行耗時):
JVM參數:-Xms512m -Xmx512m
FastDateFormat ?: 5078ms
直接使用Calendar:?3947ms
JVM參數:-server -Xms512m -Xmx512m
FastDateFormat ?: 2716ms
直接使用Calendar: 2285ms
可見在純粹的速度上FastDateFormat要比直接使用Calendar要慢,但對于服務器程序來說,開啟-server后,差距會縮小,由于是在windows上測試的,開不開-server只是和是否使用Parallel GC有關(開啟后Parallel GC很好地利用了4核cpu的優勢,減少了一部分GC時間)。又由于本次并發量很大,所以可以預見在實際應用中,對于服務器程序而言,使用FastDateFormat后,性能影響應該不是很大。