原因分析
SimpleDateFormat(下面簡稱sdf)類內部有一個Calendar對象引用,它用來儲存和這個sdf相關的日期信息,例如sdf.parse(dateStr)
, sdf.format(date)
諸如此類的方法參數傳入的日期相關String,Date等等,都是交友Calendar引用來儲存的。這樣就會導致一個問題,如果你的sdf是個static的,那么多個thread 之間就會共享這個sdf,同時也是共享這個Calendar引用,并且,觀察sdf.parse()
方法,你會發現有如下的調用:
Date parse() {
calendar.clear(); // 清理calendar
... // 執行一些操作, 設置 calendar 的日期什么的
calendar.getTime(); // 獲取calendar的時間
}
這里會導致的問題就是,如果線程A調用了sdf.parse()
,并且進行了calendar.clear()
后還未執行calendar.getTime()
的時候,線程B又調用了sdf.parse()
,這時候線程B也執行了sdf.clear()
方法,這樣就導致線程 A 的的calendar數據被清空了(實際上A,B的同時被清空了)。又或者當 A 執行了calendar.clear()
后被掛起,這時候 B 開始調用sdf.parse()
并順利結束,這樣 A 的 calendar內存儲的的date 變成了后來 B 設置的calendar的date。
解決方案
最簡單的解決方案我們可以把static去掉,這樣每個新的線程都會有一個自己的sdf實例,從而避免線程安全的問題。然而,使用這種方法,在高并發的情況下會大量的new sdf
以及銷毀sdf,這樣是非常耗費資源的,所以是不可行的。
另外一種方法可以使用Threadlocal解決此問題,對于每個線程SimpleDateFormat不存在影響他們之間協作的狀態,為每個線程創建一個SimpleDateFormat變量的拷貝或者叫做副本,代碼如下:
/**
* 使用ThreadLocal以空間換時間解決SimpleDateFormat線程安全問題。
*/
public class DateUtil {
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
@SuppressWarnings("rawtypes")
private static ThreadLocal threadLocal = new ThreadLocal() {
protected synchronized Object initialValue() {
return new SimpleDateFormat(DATE_FORMAT);
}
};
public static DateFormat getDateFormat() {
return (DateFormat) threadLocal.get();
}
public static Date parse(String textDate) throws ParseException {
return getDateFormat().parse(textDate);
}
}
創建一個ThreadLocal類變量,這里創建時用了一個匿名類,覆蓋了initialValue方法,主要作用是創建時初始化實例,也可以采用下面方式創建。
//第一次調用get將返回null
private static ThreadLocal threadLocal = new ThreadLocal();
//獲取線程的變量副本,如果不覆蓋initialValue,第一次get返回null,故需要初始化一個SimpleDateFormat,并set到threadLocal中
public static DateFormat getDateFormat() {
DateFormat df = (DateFormat) threadLocal.get();
if(df==null){
df = new SimpleDateFormat(DATE_FORMAT)
threadLocal.set(df);
}
return df;
}
通過以上方式,每個線程會實例化一個SimpleDateFormat實例,實例在線程內共享,達到了解決線程安全性的問題,一定程度上也提高了性能。