阿裡巴巴java開發手冊強制要求:
5. 【強制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為 static,必須加鎖,或者使用 DateUtils 工具類。(org.apache.commons.lang3.time.DateUtils)
因為SimpleDateFormat 繼承DateFormat,DateFormat中定義了屬性calendar。SimpleDateFormat 裡邊parse(..)方法和format(..)方法都使用到了calendar,并且使用過程中,對于cal.clear(),cal.set*(..)都沒有添加同步鎖。是以SimpleDateFormat類的對象并不适合在多線程中共用。
如下所示:

多線程中使用SimpleDateFormat:
可以在每個線程中new一個自己的SimpleDateFormat對象。(對象無法複用,頻繁的new和回收)
如果想在多線程中_共用SimpleDateFormat的對象,必須加鎖。(synchronized導緻降低多線程的效率)
可以使用import org.apache.commons.lang3.time.DateUtils進行一些時間的計算。
可以使用import org.apache.commons.lang3.time.DateFormatUtils進行日期和字元串的轉換。
阿裡巴巴java開發手冊推薦使用ThreadLocal<DateFormat>
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
後邊有代碼示例。
注意:
6. 【強制】必須回收自定義的 ThreadLocal 變量,尤其線上程池場景下,線程經常會被複用,
如果不清理自定義的 ThreadLocal 變量,可能會影響後續業務邏輯和造成記憶體洩露等問題。
(如果使用代理模式和工廠模式使用線程池的話)盡量在代理中使用 try-finally 塊進行回收。
正例:
objectThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}
如果通過new ThreadPoolExecutor(..)建立線程池的話,可以等線程執行完畢以後對ThreadLocal進行回收:threadLocal.remove();。或直接關閉線程池:threadPool.shutDown();
ThreadLocal<DateFormat>代碼示例:
public static void main(String[] args) {
ThreadLocal<SimpleDateFormat> localDateFormat = new ThreadLocal<SimpleDateFormat>();
// 定義一個線程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 10, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(20),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 線程計數器
CountDownLatch main = new CountDownLatch(12);
Runnable runnable = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
SimpleDateFormat dateFormat = localDateFormat.get();
if (null == dateFormat) {
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
localDateFormat.set(dateFormat);
System.out.println(name+" 線程建立一個新的SimpleDateFormat");
} else {
System.out.println(name+" 線程複用上次使用過的SimpleDateFormat");
}
// 線程任務執行完畢,主線程計數器減1。
main.countDown();
}
};
// 開辟線程執行任務
for (int i = 0; i < 12; i++) {
threadPool.execute(runnable);
if (i == 3 || i == 7) {
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println("=================================");
}
}
}
}
try {
// 線程阻塞,等到線程計數器歸零後繼續執行。
main.await();
System.out.println("所有線程任務執行完畢,回收localDateFormat。");
localDateFormat.remove();
System.out.println("線程池不用了。關閉線程池。如果之前沒有對localDateFormat進行回收,線程池關閉後,線程裡的localDateFormat會被回收。");
threadPool.shutdown();
} catch (InterruptedException e) {
// shutdown()會線上程任務都執行完畢後将線程池關閉。
threadPool.shutdown();
}
}
運作結果:
pool-1-thread-1 線程建立一個新的SimpleDateFormat
pool-1-thread-3 線程建立一個新的SimpleDateFormat
pool-1-thread-4 線程建立一個新的SimpleDateFormat
pool-1-thread-2 線程建立一個新的SimpleDateFormat
=================================
pool-1-thread-1 線程複用上次使用過的SimpleDateFormat
pool-1-thread-3 線程複用上次使用過的SimpleDateFormat
pool-1-thread-4 線程複用上次使用過的SimpleDateFormat
pool-1-thread-1 線程複用上次使用過的SimpleDateFormat
=================================
pool-1-thread-3 線程複用上次使用過的SimpleDateFormat
pool-1-thread-2 線程複用上次使用過的SimpleDateFormat
pool-1-thread-4 線程複用上次使用過的SimpleDateFormat
pool-1-thread-3 線程複用上次使用過的SimpleDateFormat
所有線程任務執行完畢,回收localDateFormat。
線程池不用了。關閉線程池。如果之前沒有對localDateFormat進行回收,線程池關閉後,線程裡的localDateFormat會被回收。
如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat
推薦博文:DateTimeFormatter、LocalDateTime 的使用