以前只把SimpleDateFormat類當前一個簡單的工具類使用,並沒有注意它存在的線程安全問題,直到最近在近期一個數據遷移項目中才碰到。我的遷移程序會比較遷移前和後的數據是否一致,在做這個事情的時候,由於之前的數據庫中存儲的日期使用的14位字符串,即20140502112230,而新庫中規範要求使用Timestamp類型,這自然要涉及到日期類型與字符串之間的轉化。因爲日期格式固定,因此我將這個方法封裝在了一個工具類中,並將SimpleDateFormat類的變量聲明成了一個類變量,如下:
public class Util {
// 聲明瞭一個靜態變量
static final SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss");
public static Timestamp str2TimeStamp(String datestr) {
try {
if (StringUtils.isBlank(datestr) || datestr.length() != 14) {
return null;
}
Date d = yyyyMMddHHmmss.parse(datestr);
return new Timestamp(d.getTime());
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
public static String timeStamp2String(Timestamp t) {
if (t == null) {
return null;
}
return yyyyMMddHHmmss.format(t);
}
}
在執行多線程的比較程序過程中,發現日期值老是錯誤,斷點查看,明明字符串是20140502112230,Timestamp卻出現了另外的值,才意識到SimpleDateFormat的線程安全問題,趕緊查看javadoc:
Synchronization
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
意思是說,該日期格式類不是同步的。所以建議在使用的時候,爲每個線程單獨創建一個SimpleDateForm實例。如果非要多個線程併發地訪問一個實例,那麼必須使用額外的同步。
或者我們可以把相應的對象放到ThreadLocal中,多個線程調用時使用的都是各自線程中的副本,就不會出現線程安全問題了。
public static final ThreadLocal<SimpleDateFormat> sdfThread = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));