DateFormat 類是一個非線程安全的類。 javadocs 文檔裡面提到"Date formats是不能同步的。 我們建議為每個線程創建獨立的日期格式。 如果多個線程同時訪問一個日期格式,這需要在外部加上同步代碼塊。"
以下的代碼為我們展示瞭如何在一個線程環境裡面使用DateFormat把字符串日期轉換為日期對象。創建一個實例來獲取日期格式會比較高效,因為系統不需要多次獲取本地語言和國家。
public class DateFormatTest { private final DateFormat format = new SimpleDateFormat("yyyyMMdd"); public Date convert(String source) throws ParseException{ Date d = format.parse(source); return d; }}
這段代碼是非線程安全的。我們可以通過在多個線程中調用它。在以下調用的代碼中,我創建了一個有兩個線程的線程池,並提交了5個日期轉換任務,之後查看運行結果:
final DateFormatTest t =new DateFormatTest();Callable<Date> task =new Callable<Date>(){ public Date call()throws Exception { return t.convert("20100811"); }}; //讓我們嘗試2個線程的情況ExecutorService exec = Executors.newFixedThreadPool(2);List<Future<Date>> results = new ArrayList<Future<Date>>(); //實現5次日期轉換for(int i =0; i < 5; i++){ results.add(exec.submit(task));}exec.shutdown(); //查看結果for(Future<Date> result : results){ System.out.println(result.get() );}
代碼的運行結果並非如我們所願- 有時候,它輸出正確的日期,有時候會輸出錯誤的(例如.Sat Jul 31 00:00:00 BST 2012),有些時候甚至會拋出NumberFormatException!
如何並發使用DateFormat類
我們可以有多種方法在線程安全的情況下使用DateFormat類。
1. 同步
最簡單的方法就是在做日期轉換之前,為DateFormat對象加鎖。這種方法使得一次只能讓一個線程訪問DateFormat對象,而其他線程只能等待。
public Date convert(String source) throws ParseException{ synchronized(format) { Date d = format.parse(source); return d; }}
2. 使用ThreadLocal
另外一個方法就是使用ThreadLocal變量去容納DateFormat對象,也就是說每個線程都有一個屬於自己的副本,並無需等待其他線程去釋放它。這種方法會比使用同步塊更高效。
public class DateFormatTest { private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd"); } }; public Date convert(String source) throws ParseException { Date d = df.get().parse(source); return d; }}
3. Joda-Time
Joda-Time 是一個很棒的開源的JDK 的日期和日曆API 的替代品,其DateTimeFormat 是線程安全而且不變的。
import org.joda.time.DateTime;import org.joda.time.format.DateTimeFormat;import org.joda.time.format.DateTimeFormatter;import java.util.Date; public class DateFormatTest { private final DateTimeFormatter fmt = DateTimeFormat.forPattern ("yyyyMMdd"); public Date convert(String source){ DateTime d = fmt.parseDateTime(source); returnd.toDate(); }}