DateFormat 類是一個非線程安全的類。javadocs 文檔里面提到"Date formats是不能同步的。 我們建議為每個線程創建獨立的日期格式。 如果多個線程同時訪問一個日期格式,這需要在外部加上同步代碼塊。"
以下的代碼為我們展示了如何在一個線程環境里面使用DateFormat把字符串日期轉換為日期對象。創建一個實例來獲取日期格式會比較高效,因為系統不需要多次獲取本地語言和國家。
1
2
3
4
5
6
7
8
9
10
11
|
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個日期轉換任務,之后查看運行結果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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對象,而其他線程只能等待。
1
2
3
4
5
6
7
|
public Date convert(String source) throws ParseException{ synchronized (format) { Date d = format.parse(source); return d; } } |
2. 使用ThreadLocal
另外一個方法就是使用ThreadLocal變量去容納DateFormat對象,也就是說每個線程都有一個屬于自己的副本,并無需等待其他線程去釋放它。這種方法會比使用同步塊更高效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 是線程安全而且不變的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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(); } } |