原因
由源码可以知道,SimpleDateFormat 类的数据时保存在类中的,如果我们在自己的类开始就声明public static final SimpleDateFormat SDF = new SimpleDateFormat("MMdd");的话,那么SimpleDateFormat 内部数据就会混乱。出现日期转换错误。
测试
使用单元测试,在多线程中对两个日期操作,当只剩下最后一个线程的时候,错误不会再出现。
package com.ch.dcs.sync.core.test;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
/**
* Created by 002387 on 2017/2/3.
*/
public class DateFormatTest {
public static final SimpleDateFormat SDF = new SimpleDateFormat("MMdd");
@Test
public void test1(){
Calendar c = Calendar.getInstance();
Date d1 = c.getTime();
c.add(Calendar.DATE, -3);
Date d2 = c.getTime();
final String s1 = SDF.format(d1);
final String s2 = SDF.format(d2);
System.out.println(String.format("test date %s and %s", s1, s2));
final Random ran = new Random();
final AtomicLong count = new AtomicLong(0);
for (int i = 0; i < 3; i++) {
new Thread() {
@Override
public void run() {
for (;;) {
count.addAndGet(1);
boolean flag = ran.nextBoolean();
Date d = flag ? d1 : d2;
String s = flag ? s1 : s2;
String r = SDF.format(d);
if(!s.equals(r)){
System.out.println(String.format("== count %s date %s format to %s", count.get(), d.toString(), r));
throw new RuntimeException("");
} else {
// System.out.println(String.format("thread %s date %s format to %s", Thread.currentThread(), d.toString(), r));
}
}
}
}.start();
}
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
会抛出RuntimeException异常,当线程数只剩下一的时候,异常消失,同时数据也不会出现错误, 结果:
test date 0203 and 0131
== count 21 date Fri Feb 03 16:16:12 CST 2017 format to 0231
== count 50 date Fri Feb 03 16:16:12 CST 2017 format to 0231
Exception in thread "Thread-0" java.lang.RuntimeException:
at com.ch.dcs.sync.core.test.DateFormatTest$1.run(DateFormatTest.java:41)
Exception in thread "Thread-2" java.lang.RuntimeException:
at com.ch.dcs.sync.core.test.DateFormatTest$1.run(DateFormatTest.java:41)
Process finished with exit code 1
###解决办法
1.使用局部变量,每个线程中声明自己的对象,但是消耗太大,不可取。 2.使用 ThreadLocal,这里每个线程将有它自己的 SimpleDateFormat 副本。
private static final ThreadLocal<DateFormat> FORMAT = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMddHHmmss");
}
};
//获取对象的时候使用get()方法如下
ORMAT.get().parse("20161010000000");
3.使用同步锁