SimpleDateFormat在進行日期格式轉換時用的不少,可是 DateFormat 和 SimpleDateFormat 類不都是線程安全的,在多線程環境下調用 format() 和 parse() 方法應該使用同步代碼來避免問題html
* <p> * Date formats are not synchronized. * It is recommended to create separate format instances for each thread. * If multiple threads access a format concurrently, it must be synchronized * externally. * * @see <a href="http://java.sun.com/docs/books/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a> * @see java.util.Calendar * @see java.util.TimeZone * @see DateFormat * @see DateFormatSymbols * @author Mark Davis, Chen-Lieh Huang, Alan Liu */ public class SimpleDateFormat extends DateFormat { ................... }
上面的註釋已經說的很清楚,日期格式化操做不是同步的,建議爲每一個線程穿件單獨的實例java
public static final String PATTEN = "yyyy-MM-dd hh:mm:ss"; public static final SimpleDateFormat sdf = new SimpleDateFormat(PATTEN); public static final CountDownLatch countDownLatch = new CountDownLatch(100); @Test public void testJDKSimpleDateFormat() { //jdk的實現方式,會有線程安全的問題 for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { try { System.out.println(sdf.parseObject("2013-05-24 06:02:20")); } catch (ParseException e) { e.printStackTrace(); } } }).start(); } }
上面的測試方法在多線程下會出現以下異常:git
Exception in thread "Thread-1" Exception in thread "Thread-3" java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:453) at java.lang.Long.parseLong(Long.java:483) at java.text.DigitList.getLong(DigitList.java:194) at java.text.DecimalFormat.parse(DecimalFormat.java:1316) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455) at java.text.DateFormat.parseObject(DateFormat.java:415) at java.text.Format.parseObject(Format.java:243) at com.smart.tools.SimpleDateFormatTest$1.run(SimpleDateFormatTest.java:32) at java.lang.Thread.run(Thread.java:745) Exception in thread "Thread-8" java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:453) at java.lang.Long.parseLong(Long.java:483) at java.text.DigitList.getLong(DigitList.java:194) at java.text.DecimalFormat.parse(DecimalFormat.java:1316) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2088) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455) at java.text.DateFormat.parseObject(DateFormat.java:415) at java.text.Format.parseObject(Format.java:243) at com.smart.tools.SimpleDateFormatTest$1.run(SimpleDateFormatTest.java:32) at java.lang.Thread.run(Thread.java:745) java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:453) at java.lang.Long.parseLong(Long.java:483) at java.text.DigitList.getLong(DigitList.java:194) at java.text.DecimalFormat.parse(DecimalFormat.java:1316) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455) at java.text.DateFormat.parseObject(DateFormat.java:415) at java.text.Format.parseObject(Format.java:243) at com.smart.tools.SimpleDateFormatTest$1.run(SimpleDateFormatTest.java:32) at java.lang.Thread.run(Thread.java:745) Fri May 24 06:02:20 CST 2013 Fri May 24 06:02:20 CST 2013 Fri May 24 06:02:20 CST 2013
SimpleDateFormat繼承了DateFormat,在DateFormat中定義了一個protected屬性的 Calendar類的對象:calendar。只是由於Calendar累的概念複雜,牽扯到時區與本地化等等,Jdk的實現中使用了成員變量來傳遞參數,這就形成在多線程的時候會出現錯誤。apache
在format方法裏,有這樣一段代碼:api
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // Convert input date to time field list calendar.setTime(date);
calendar.setTime(date)這條語句改變了calendar,稍後,calendar還會用到(在subFormat方法裏),而這就是引起問題的根源。想象一下,在一個多線程環境下,有兩個線程持有了同一個SimpleDateFormat的實例,分別調用format方法:
線程1調用format方法,改變了calendar這個字段。
中斷來了。
線程2開始執行,它也改變了calendar。
又中斷了。
線程1回來了,此時,calendar已然不是它所設的值,而是走上了線程2設計的道路。若是多個線程同時爭搶calendar對象,則會出現各類問題,時間不對,線程掛死等等。
分析一下format的實現,咱們不難發現,用到成員變量calendar,惟一的好處,就是在調用subFormat時,少了一個參數,卻帶來了這許多的問題。其實,只要在這裏用一個局部變量,一路傳遞下去,全部問題都將迎刃而解。
這個問題背後隱藏着一個更爲重要的問題--無狀態:無狀態方法的好處之一,就是它在各類環境下,均可以安全的調用。衡量一個方法是不是有狀態的,就看它是否改動了其它的東西,好比全局變量,好比實例的字段。format方法在運行過程當中改動了SimpleDateFormat的calendar字段,因此,它是有狀態的。安全
解決辦法多線程
一、每次須要的時候都建立一個新的實例maven
public static String formatDate(Date date)throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } public static Date parse(String strDate) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.parse(strDate); }
2.使用同步:同步SimpleDateFormat對象ide
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException{ synchronized(sdf){ return sdf.format(date); } } public static Date parse(String strDate) throws ParseException{ synchronized(sdf){ return sdf.parse(strDate); } }
三、使用common-lang中的api
測試
maven依賴:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency>
package com.smart.tools; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.CountDownLatch; import org.apache.commons.lang3.time.FastDateFormat; import org.junit.Test; /** * 測試時間處理類的線程安全問題 * @Description * @author gaowenming */ public class SimpleDateFormatTest { public static final String PATTEN = "yyyy-MM-dd hh:mm:ss"; public static final SimpleDateFormat sdf = new SimpleDateFormat(PATTEN); public static final CountDownLatch countDownLatch = new CountDownLatch(100); @Test public void testJDKSimpleDateFormat() { //jdk的實現方式,會有線程安全的問題 for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { try { System.out.println(sdf.parseObject("2013-05-24 06:02:20")); } catch (ParseException e) { e.printStackTrace(); } } }).start(); } } @Test public void testCommonLang() { //CommonLang第三方jar包實現 for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { try { FastDateFormat fdf = FastDateFormat.getInstance(PATTEN); System.out.println(fdf.format(new Date())); Date date = fdf.parse("2013-05-24 06:02:20"); System.out.println(date); countDownLatch.countDown(); } catch (Exception e) { e.printStackTrace(); } } }).start(); } try { //等待100個線程都執行完 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); } }
上面用了CountDownLatch,爲了能觀察到線程中的打印結果。