先说结论,在java中SimpleDateFormat日期格式对象是非线程安全的,如果把SimpleDateFormat对象用static关键字修饰,那么在多线程中使用这同一个对象,是有可能会出错的。
下面用代码演示一下线程不安全的情况:
public class SimpleDataFormatTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
int cpuNum = 2;// Runtime.getRuntime().availableProcessors();
CountDownLatch latch = new CountDownLatch(cpuNum);
for (int i = 0; i < cpuNum; i++) {
new Thread(() -> {
try {
TimeUnit.MICROSECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
String beforeDateStr = sdf.format(new Date());
Date date = null;
try {
date = sdf.parse(beforeDateStr);
} catch (ParseException e) {
e.printStackTrace();
return;
}
String afterDateStr = sdf.format(date);
System.out.println(Thread.currentThread().getName() + "-->beforeDateStr=" + beforeDateStr
+ ",afterDateStr=" + afterDateStr);
}).start();
latch.countDown();
}
}
}
运行结果:
Thread-0-->beforeDateStr=2020-10-28 17:04:55,afterDateStr=2020-10-28 17:04:55
Thread-1-->beforeDateStr=2020-10-28 17:04:55,afterDateStr=2020-10-28 17:04:55
Thread-1-->beforeDateStr=2020-10-28 17:05:51,afterDateStr=2020-10-28 00:00:00
Thread-0-->beforeDateStr=2020-10-28 17:05:51,afterDateStr=1101-05-30 15:05:51
Exception in thread "Thread-1" Thread-0-->beforeDateStr=2020-10-28 17:05:30,afterDateStr=2113-08-28 17:05:30
java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:601)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at learnbymaven.datetime.SimpleDataFormatTest.lambda$0(SimpleDataFormatTest.java:35)
at java.lang.Thread.run(Thread.java:748)
把上面的程序多运行几次,这三种情况都会重现出来。
那么为什么会出现这种情况呢,看一下SimpleDateFormat的部分源码:
protected Calendar calendar;
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos)
{
pos.beginIndex = pos.endIndex = 0;
return format(date, toAppendTo, pos.getFieldDelegate());
}
// 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);
// ===========================
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
可以看到,format方法中,调用了 calendar.setTime(date)代码设置时间,由于SimpleDateFormat对象使用static关键字修饰,此时calendar也相当于是共享变量了,因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象的话,调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。
同时,parse方法也不是线程安全的。
下面例举一下常见的解决方法:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String beforeDateStr = sdf.format(new Date());
String beforeDateStr = null;
String afterDateStr = null;
synchronized (SimpleDateFormat.class) {
beforeDateStr = sdf.format(new Date());
Date date = null;
try {
date = sdf.parse(beforeDateStr);
} catch (ParseException e) {
e.printStackTrace();
return;
}
afterDateStr = sdf.format(date);
}
System.out.println(Thread.currentThread().getName() + "-->beforeDateStr=" + beforeDateStr
+ ",afterDateStr=" + afterDateStr);
public class SimpleDataFormatTest {
private static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<>();
public static void main(String[] args) {
int cpuNum = 2;// Runtime.getRuntime().availableProcessors();
CountDownLatch latch = new CountDownLatch(cpuNum);
for (int i = 0; i < cpuNum; i++) {
new Thread(() -> {
try {
TimeUnit.MICROSECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
SimpleDateFormat sdf = getSimpleDateFormat();
String beforeDateStr = sdf.format(new Date());
Date date = null;
try {
date = sdf.parse(beforeDateStr);
} catch (ParseException e) {
e.printStackTrace();
return;
}
String afterDateStr = sdf.format(date);
System.out.println(Thread.currentThread().getName() + "-->beforeDateStr=" + beforeDateStr
+ ",afterDateStr=" + afterDateStr);
}).start();
latch.countDown();
}
}
private static SimpleDateFormat getSimpleDateFormat() {
if (tl.get() == null) {
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
// 为了演示效果,保证SimpleDateFormat对象每次都是从ThreadLocal中获取的
return tl.get();
}
}
public class SimpleDataFormatTest {
private final static DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
int cpuNum = Runtime.getRuntime().availableProcessors();
CountDownLatch latch = new CountDownLatch(cpuNum);
for (int i = 0; i < cpuNum; i++) {
new Thread(() -> {
try {
TimeUnit.MICROSECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
String beforeDateStr = LocalDateTime.now().format(df);
LocalDateTime date = null;
date = LocalDateTime.parse(beforeDateStr, df);
String afterDateStr = date.format(df);
System.out.println(Thread.currentThread().getName() + "-->beforeDateStr=" + beforeDateStr
+ ",afterDateStr=" + afterDateStr);
}).start();
latch.countDown();
}
}
}