SimpleDateFormat 是一個以與語言環境有關的方式來格式化和解析日期的具體類。它容許進行格式化(日期 -> 文本)、解析(文本 -> 日期)和規範化。 SimpleDateFormat 使得能夠選擇任何用戶定義的日期-時間格式的模式。可是,仍然建議經過 DateFormat 中的 getTimeInstance、getDateInstance 或 getDateTimeInstance
來建立日期-時間格式器。每個這樣的類方法都可以返回一個以默認格式模式初始化的日期/時間格式器。能夠根據須要使用 applyPattern 方法來修改格式模式。
同步
日期格式是不一樣步的。建議爲每一個線程建立獨立的格式實例。若是多個線程同時訪問一個格式,則它必須是外部同步的。
上圖中,SimpleDateFormat類中,有個對象calendarhtml
calendar
DateFormat
使用 calendar 來生成實現日期和時間格式化所需的時間字段值。java
當SimpleDateFormat用static申明,多個線程共享SimpleDateFormat對象是,也共享該對象的calendar對象。而當調用parse方法時,會clear全部日曆字段和值。當線程A正在調用parse,線程B調用clear,這樣解析後的數據就會出現誤差api
//parse方法 @Override public Date parse(String text, ParsePosition pos) { try { parsedDate = calb.establish(calendar).getTime(); ... } }
//establish方法 Calendar establish(Calendar cal) { ... //將此 Calendar 的全部日曆字段值和時間值(從曆元至如今的毫秒偏移量)設置成未定義 cal.clear(); }
一樣 formart中也用到了calendar對象,將date設置到日曆的時間字段中安全
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // Convert input date to time field list calendar.setTime(date); ... }
當線程A調用setTime,而線程B也調用setTime,這時候線程A最後獲得的時間是 最後線程B的時間。也會致使數據誤差app
不安全示例:ide
public static void main(String[] args) throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = "1111-11-11 11:11:11"; ExecutorService executorService = Executors.newFixedThreadPool(100); for(int i=0;i<100;i++) { executorService.submit(new Runnable() { @Override public void run() { try { //多個線程操做同一個sdf對象 System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName()); } catch (ParseException e) { System.out.println("--------------> error, " + e.getMessage()); } } }); } executorService.shutdown(); }
執行結果:性能
... 1111-11-11 11:11:11---pool-1-thread-69 0011-11-11 11:11:11---pool-1-thread-72 0011-11-11 11:11:11---pool-1-thread-71 1111-11-11 11:11:11---pool-1-thread-73 1111-11-11 11:11:11---pool-1-thread-75 1111-11-11 11:11:11---pool-1-thread-76 1111-11-11 11:11:11---pool-1-thread-89 1111-11-11 00:11:11---pool-1-thread-93 1111-11-11 11:11:11---pool-1-thread-96 ...
能夠看到數據出現誤差spa
解決方案.net
1.爲每一個實例建立一個單獨的SimpleDateFormat對象線程
public static void main(String[] args) throws InterruptedException { //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = "1111-11-11 11:11:11"; ExecutorService executorService = Executors.newFixedThreadPool(100); for(int i=0;i<100;i++) { executorService.submit(new Runnable() { //爲每一個線程建立本身的sdf對象 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void run() { try { System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName()); } catch (ParseException e) { System.out.println("--------------> error, " + e.getMessage()); } } }); } executorService.shutdown(); }
缺點:每次new一個實例,都會new一個format對象,虛擬機內存消耗大,垃圾回收頻繁
2.給靜態SimpleDateFormat對象加鎖,使用Lock或者synchronized修飾
public static void main(String[] args) throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = "1111-11-11 11:11:11"; ExecutorService executorService = Executors.newFixedThreadPool(100); for(int i=0;i<100;i++) { executorService.submit(new Runnable() { @Override public void run() { //加同步鎖 synchronized (sdf) { try { System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName()); } catch (ParseException e) { System.out.println("--------------> error, " + e.getMessage()); } } } }); } executorService.shutdown(); }
缺點:性能差,其餘線程要等待鎖釋放
3.使用ThreadLocal爲每一個線程建立一個SimpleDateFormat對象副本,有線程隔離性,各自的副本對象也不會被其餘線程影響
public static void main(String[] args) throws InterruptedException { //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //初始化threadLocal並設置值 ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; String dateStr = "1111-11-11 11:11:11"; ExecutorService executorService = Executors.newFixedThreadPool(100); for(int i=0;i<100;i++) { executorService.submit(new Runnable() { @Override public void run() { try { System.out.println(threadLocal.get().format(threadLocal.get().parse(dateStr)) + "---" + Thread.currentThread().getName()); } catch (ParseException e) { System.out.println("--------------> error, " + e.getMessage()); } } }); } executorService.shutdown();
//清理threadLocal,生產環境不清理容易致使內存溢出
threadLocal.remove();
}
ThreadLocal原理分析