记录一次SimpleDateFormat的线程安全问题;
问题的出现是我定义了一个静态的 :
private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
然后在使用的时候是线程池调用:具体代码我就不贴了,写一个小的模拟案例来大概是这样的:
public class Test {
// 定义静态的SimpleDateFormat
private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 定义一个线程池
ExecutorService executorService = new ThreadPoolExecutor(0, 50,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
public void testSimpleDate() {
// 待转换的字符集
List<String> dateStrList = new ArrayList<>();
dateStrList.add("2018-04-01 10:00:01");
dateStrList.add("2018-04-02 11:00:02");
dateStrList.add("2018-04-03 12:00:03");
dateStrList.add("2018-04-04 13:00:04");
dateStrList.add("2018-04-05 14:00:05");
// 模拟线程
dateStrList.forEach((dateStr)->{
executorService.execute(()->{
try {
// 执行具体的parse方法
SDF.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
});
});
}
public static void main(String[] args) {
new Test().testSimpleDate();
}
}
运行报如下错误:
Exception in thread "pool-1-thread-3" java.lang.NumberFormatException: empty String
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
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 com.ant.test.Test.lambda$null$0(Test.java:45)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
这里我们发现在执行parse()方法的时候,程序报错了java.lang.NumberFormatException,按理说,我一个静态的类,怎么会导致线程安全问题呢? 那么是什么原因导致这个问题:那么我们跟到parse()方法里去,看到底发生了什么?
/**
* Parses text from the beginning of the given string to produce a date.
* The method may not use the entire text of the given string.
* <p>
* See the {@link #parse(String, ParsePosition)} method for more information
* on date parsing.
*
* @param source A <code>String</code> whose beginning should be parsed.
* @return A <code>Date</code> parsed from the string.
* @exception ParseException if the beginning of the specified string
* cannot be parsed.
*/
public Date parse(String source) throws ParseException
{
ParsePosition pos = new ParsePosition(0);
Date result = parse(source, pos);
if (pos.index == 0)
throw new ParseException("Unparseable date: \"" + source + "\"" ,
pos.errorIndex);
return result;
}
看这行代码好像也没问题,但是我们看DateFormat这个类结构:
public abstract class DateFormat extends Format {
/**
* The {@link Calendar} instance used for calculating the date-time fields
* and the instant of time. This field is used for both formatting and
* parsing.
*
* <p>Subclasses should initialize this field to a {@link Calendar}
* appropriate for the {@link Locale} associated with this
* <code>DateFormat</code>.
* @serial
*/
protected Calendar calendar;
发现类里面除了继承Format类外,还有一个Calendar类属性,那么问题是不是就出在这个Calendar中呢?,我们跟到parse()方法的Date result = parse(source, pos);里面去看一下:
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();
}
}
}
parsedDate = calb.establish(calendar).getTime();然后再看这个方法调用:
Calendar establish(Calendar cal) {
boolean weekDate = isSet(WEEK_YEAR)
&& field[WEEK_YEAR] > field[YEAR];
if (weekDate && !cal.isWeekDateSupported()) {
// Use YEAR instead
if (!isSet(YEAR)) {
set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
}
weekDate = false;
}
cal.clear();
// Set the fields from the min stamp to the max stamp so that
// the field resolution works in the Calendar.
for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
for (int index = 0; index <= maxFieldIndex; index++) {
if (field[index] == stamp) {
cal.set(index, field[MAX_FIELD + index]);
break;
}
}
}
在这里有一个cal.clear();它这里把我们传进来的calender给clear了,然后后面又给他重新复制返回出去;所以当一个线程执行clear,这是另一个线程也进来,那么这个calender已经被清空了,所以下一个线程进来再用calender时就会发生错误了;
这就是SimpleDateFormate的线程安全问题,后面注意就可以了: