SimpleDateFormat 是一個非線程安全的類,若是在多線程下做爲變量使用,須要特別注意線程安全問題,否則會出現莫名其妙的問題 ##多線程下問題重現html
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestSimpleDateFormat implements Runnable{ private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); private String date; public TestSimpleDateFormat(String date){ this.date = date; } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(2); es.execute(new TestSimpleDateFormat("2017-04-10")); es.execute(new TestSimpleDateFormat("2017-05-10")); } @Override public void run() { Date now = null; try { now = sdf.parse(this.date); } catch (ParseException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId()+"格式化日期"+sdf.format(now)); } }
輸出的結果可能各不相同java
13格式化日期2200-05-10 14格式化日期2200-05-10
Exception in thread "pool-1-thread-2" java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) at java.text.DigitList.getDouble(DigitList.java:169) at java.text.DecimalFormat.parse(DecimalFormat.java:2056) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at TestSimpleDateFormat.run(TestSimpleDateFormat.java:27) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
SimpleDateFormat並不是是同步的,建議每一個線程建立單獨的實例。在多線程併發訪問format實例的時候,必須加鎖。 ##formatapi
// Called from Format after creating a FieldDelegate private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { .......... // Convert input date to time field list calendar.setTime(date); ......... }
##parse安全
Date parsedDate; try { parsedDate = calb.establish(calendar).getTime(); // If the year value is ambiguous, // then the two-digit year == the default start year if (ambiguousYear[0]) { if (parsedDate.before(defaultCenturyStart)) { parsedDate = calb.addYear(100).establish(calendar).getTime(); } } }
Calendar establish(Calendar cal) { ........... cal.clear(); .......... }
SimpleDateFormat繼承自DateFormat,DateFormat內有一個屬性字段Calendar實例。若是在多線程環境下使用,每一個線程會共享這個實例. 假設線程A調用parse()方法,並進行了calendar.clear()後還未執行calendar.getTime()的時候,線程B又調用parse(),這時候線程B也執行了sdf.clear()方法,這樣就會致使線程A的calendar數據被清空或者A執行calendar.clear()後被掛起,這是B開始調用sdf.parse()並順利結束,這樣A的calendar內存儲的date變成了B設置的calendar的date
##解決方法 ###1.使用ThreadLocal 每一個線程使用本身線程獨立的simpledateformat實例多線程
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestSimpleDateFormat implements Runnable{ private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() { protected synchronized SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; private String date; public TestSimpleDateFormat(String date){ this.date = date; } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(2); es.execute(new TestSimpleDateFormat("2017-04-10")); es.execute(new TestSimpleDateFormat("2017-05-10")); } @Override public void run() { SimpleDateFormat sdf = threadLocal.get(); Date now = null; try { now = sdf.parse(this.date); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getId()+"格式化日期"+sdf.format(now)); } }
13格式化日期2017-04-10 14格式化日期2017-05-10
###2.多線程下每一次都從新new出一個新對象併發
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestSimpleDateFormat implements Runnable{ private String date; public TestSimpleDateFormat(String date){ this.date = date; } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(2); es.execute(new TestSimpleDateFormat("2017-04-10")); es.execute(new TestSimpleDateFormat("2017-05-10")); } @Override public void run() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date now = null; try { now = sdf.parse(this.date); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getId()+"格式化日期"+sdf.format(now)); } }
###3.使用時對SimpleDateFormat加鎖oracle
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestSimpleDateFormat implements Runnable{ private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); private String date; public TestSimpleDateFormat(String date){ this.date = date; } public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(2); es.execute(new TestSimpleDateFormat("2017-04-10")); es.execute(new TestSimpleDateFormat("2017-05-10")); } @Override public void run() { synchronized (sdf) { Date now = null; try { now = sdf.parse(this.date); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getId()+"格式化日期"+sdf.format(now)); } } }