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 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));
}
}
输出的结果可能各不相同
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实例的时候,必须加锁。 ##format
// 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加锁
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));
}
}
}