在Java中,格式化日期通常使用SimpleDateFormat这个类。
我们知道,SimpleDateFormat是线程不安全的,主要原因是format方法内部调用calendar.setTime方法,整个过程都是没有加锁或同步的,如果同时有多个线程调用到这一步,则会出现线程安全问题。
public final String format(Date date) {
return format(date, new StringBuffer(), DontCareFieldPosition.INSTANCE).toString();
}
// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
...
}
所以,大部分时候则是在方法内部new出新的DateFormat对象再做格式化,如:DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
。
但在高访问量的情况下,频繁创建实例也会导致内存开销大和GC频繁问题。
Apache的commons-lang包下有个FastDateFormat可以方便的解决上述问题。
查看其源码:
// 2.6版本源码
public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
if (format == null) {
format = emptyFormat;
format.init(); // convert shell format into usable one
cInstanceCache.put(format, format); // this is OK!
}
return format;
}
FastDateFormat通过getInstance静态方法获取对象,内部加了一个cInstanceCache缓存。当使用同样的pattern格式化时会命中缓存,返回缓存中的对象,避免重复创建实例。
拿到对象调用format方法,可以看出Calender是在format方法中创建的,所以不会出现setTime的线程安全问题。
public String format(Date date) {
Calendar c = new GregorianCalendar(mTimeZone, mLocale);
c.setTime(date);
return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
}
而且getInstance这个方法是加锁的,以保证获取实例的线程安全性。在新的版本(3.0以上)中getInstance方法内更加使用ConcurrentMap做缓存提高并发性能。
// 3.0版本源码
abstract class FormatCache<F extends Format> {
static final int NONE = -1;
private final ConcurrentMap<MultipartKey, F> cInstanceCache;
private final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache;
...
}
private static final FormatCache<FastDateFormat> cache = new FormatCache() {
protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) {
return new FastDateFormat(pattern, timeZone, locale);
}
};
public static FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
return (FastDateFormat)cache.getInstance(pattern, timeZone, locale);
}
基于上面FastDateFormat类,commons-lang包还提供了DateFormatUtils工具类,提供了两方面的功能: