原因分析
SimpleDateFormat(下面簡稱sdf)類內(nèi)部有一個Calendar對象引用羊赵,它用來儲存和這個sdf相關(guān)的日期信息趟佃,例如sdf.parse(dateStr)
, sdf.format(date)
諸如此類的方法參數(shù)傳入的日期相關(guān)String昧捷,Date等等闲昭,都是交友Calendar引用來儲存的。這樣就會導(dǎo)致一個問題靡挥,如果你的sdf是個static的序矩,那么多個thread 之間就會共享這個sdf,同時也是共享這個Calendar引用芹血,并且贮泞,觀察sdf.parse()
方法,你會發(fā)現(xiàn)有如下的調(diào)用:
Date parse() {
calendar.clear(); // 清理calendar
... // 執(zhí)行一些操作, 設(shè)置 calendar 的日期什么的
calendar.getTime(); // 獲取calendar的時間
}
這里會導(dǎo)致的問題就是幔烛,如果線程A調(diào)用了sdf.parse()
啃擦,并且進行了calendar.clear()
后還未執(zhí)行calendar.getTime()
的時候,線程B又調(diào)用了sdf.parse()
饿悬,這時候線程B也執(zhí)行了sdf.clear()
方法令蛉,這樣就導(dǎo)致線程 A 的的calendar數(shù)據(jù)被清空了(實際上A,B的同時被清空了)。又或者當 A 執(zhí)行了calendar.clear()
后被掛起珠叔,這時候 B 開始調(diào)用sdf.parse()
并順利結(jié)束蝎宇,這樣 A 的 calendar內(nèi)存儲的的date 變成了后來 B 設(shè)置的calendar的date。
解決方案
最簡單的解決方案我們可以把static去掉祷安,這樣每個新的線程都會有一個自己的sdf實例姥芥,從而避免線程安全的問題。然而汇鞭,使用這種方法凉唐,在高并發(fā)的情況下會大量的new sdf
以及銷毀sdf,這樣是非常耗費資源的霍骄,所以是不可行的台囱。
另外一種方法可以使用Threadlocal解決此問題,對于每個線程SimpleDateFormat不存在影響他們之間協(xié)作的狀態(tài)读整,為每個線程創(chuàng)建一個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);
}
}
創(chuàng)建一個ThreadLocal類變量米间,這里創(chuàng)建時用了一個匿名類强品,覆蓋了initialValue方法,主要作用是創(chuàng)建時初始化實例车伞,也可以采用下面方式創(chuàng)建择懂。
//第一次調(diào)用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實例蹦哼,實例在線程內(nèi)共享鳄哭,達到了解決線程安全性的問題,一定程度上也提高了性能纲熏。