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后,性能影响应该不是很大。