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)); } } }