轉載來自:http://blog.csdn.net/zxh87/article/details/19414885java
DateFormat和SimpleDateFormat都不是線程安全的。在多線程環境中調用format()和parse()應處理線程安全的問題。緩存
每次處理一個時間信息,都新建一個SimpleDateFormat實例,而後再丟棄。形成內存的浪費。安全
1 package com.peidasoft.dateformat; 2 3 import java.text.ParseException; 4 import java.text.SimpleDateFormat; 5 import java.util.Date; 6 7 public class DateUtil { 8 9 public static String formatDate(Date date)throws ParseException{ 10 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 11 return sdf.format(date); 12 } 13 14 public static Date parse(String strDate) throws ParseException{ 15 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 16 return sdf.parse(strDate); 17 } 18 }
爲了防止頻繁建立,使用靜態的SimpleDateFormat實例,全部關於時間的處理都使用這個靜態實例。多線程
缺點是:在多線程環境中會有問題,好比轉換的時間不對,線程被掛死或者報奇怪的錯誤等等。都是由於SimpleDateFormat線程不安全形成的。併發
1 package com.peidasoft.dateformat; 2 3 import java.text.ParseException; 4 import java.text.SimpleDateFormat; 5 import java.util.Date; 6 7 public class DateUtil { 8 private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 9 10 public static String formatDate(Date date)throws ParseException{ 11 return sdf.format(date); 12 } 13 14 public static Date parse(String strDate) throws ParseException{ 16 return sdf.parse(strDate); 17 } 18 }
JDK文檔中對於DateFormat的說明:app
SimpleDateFormat中的日期格式不是同步的。推薦(建議)爲每一個線程建立獨立的格式實例。若是多個線程同時訪問一個格式,則它必須保持外部同步。ide
1 Synchronization: 2 Date formats are not synchronized. 3 It is recommended to create separate format instances for each thread. 4 If multiple threads access a format concurrently, it must be synchronized externally.
SimpleDateFormat繼承了DateFormat。在DateFormat中定義了一個Calendar類的對象calendar。由於Calendar累的概念複雜,牽扯到時區與本地化等等,因此Jdk的實現中使用了成員變量來傳遞參數。性能
format方法以下:學習
1 private StringBuffer format(Date date, StringBuffer toAppendTo, 2 FieldDelegate delegate) { 4 calendar.setTime(date); 6 boolean useDateFormatSymbols = useDateFormatSymbols(); 7 8 for (int i = 0; i < compiledPattern.length; ) { 9 int tag = compiledPattern[i] >>> 8; 10 int count = compiledPattern[i++] & 0xff; 11 if (count == 255) { 12 count = compiledPattern[i++] << 16; 13 count |= compiledPattern[i++]; 14 } 15 16 switch (tag) { 17 case TAG_QUOTE_ASCII_CHAR: 18 toAppendTo.append((char)count); 19 break; 20 21 case TAG_QUOTE_CHARS: 22 toAppendTo.append(compiledPattern, i, count); 23 i += count; 24 break; 25 26 default: 27 subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); 28 break; 29 } 30 } 31 return toAppendTo; 32 }
calendar.setTime(date)這條語句改變了calendar,稍後,calendar還會用到(在subFormat方法裏),而這就是引起問題的根源。測試
在一個多線程環境下,有兩個線程持有了同一個SimpleDateFormat的實例,分別調用format方法:
若是多個線程同時爭搶calendar對象,則會出現各類問題,時間不對,線程掛死等等。
這個問題背後隱藏着一個更爲重要的問題--無狀態。
無狀態方法的好處之一,就是它在各類環境下,均可以安全的調用。衡量一個方法是不是有狀態的,就看它是否改動了其它的東西,好比全局變量,好比實例的字段。format方法在運行過程當中改動了SimpleDateFormat的calendar字段,因此它是有狀態的。
這也同時提醒咱們在開發和設計系統的時候注意下一下三點:
1 public class DateSyncUtil { 2 3 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 4 5 public static String formatDate(Date date)throws ParseException{ 6 synchronized(sdf){ 7 return sdf.format(date); 8 } 9 } 10 11 public static Date parse(String strDate) throws ParseException{ 12 synchronized(sdf){ 13 return sdf.parse(strDate); 14 } 15 } 16 }
使用ThreadLocal, 也是將共享變量變爲獨享,線程獨享確定能比方法獨享在併發環境中能減小很多建立對象的開銷。若是對性能要求比較高的狀況下,通常推薦使用這種方法。
寫法一:
1 package com.peidasoft.dateformat; 2 3 import java.text.DateFormat; 4 import java.text.ParseException; 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 8 public class ConcurrentDateUtil { 9 10 private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { 11 @Override 12 protected DateFormat initialValue() { 13 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 14 } 15 }; 16 17 public static Date parse(String dateStr) throws ParseException { 18 return threadLocal.get().parse(dateStr); 19 } 20 21 public static String format(Date date) { 22 return threadLocal.get().format(date); 23 } 24 }
寫法二:
1 public class ThreadLocalDateUtil { 2 private static final String date_format = "yyyy-MM-dd HH:mm:ss"; 3 private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
4 public static DateFormat getDateFormat(){ 6 DateFormat df = threadLocal.get(); 7 if(df==null){ 8 df = new SimpleDateFormat(date_format); 9 threadLocal.set(df); 10 } 11 return df; 12 } 13 14 public static String formatDate(Date date) throws ParseException { 15 return getDateFormat().format(date); 16 } 17 18 public static Date parse(String strDate) throws ParseException { 19 return getDateFormat().parse(strDate); 20 } 21 }
作一個簡單的壓力測試,方法一最慢,方法三最快。
可是就算是最慢的方法一性能也不差,通常系統方法一和方法二就能夠知足,因此說在這個點很難成爲系統的瓶頸所在。從簡單的角度來講,建議使用方法一或者方法二,若是在必要的時候,追求那麼一點性能提高的話,能夠考慮用方法三,用ThreadLocal作緩存。
ps:Joda-Time類庫對時間處理方式比較完美,建議使用。(待學習)