SimpleDateFormat为什么是线程不安全的?
前言
其实这已经是个老生常谈的问题了,这篇博客旨在提醒自己,方便回忆。同时希望可以帮助到需要的伙伴。如果有哪些地方有错误或不合适,欢迎大家指出。
我不知道它是线程不安全的之前,在做日期格式转换的时候,我对它情有独钟。直到有一天代码Review的时候,老大告诉这玩意是线程不安全!
为啥?看看去!
原因
简明意赅的说就是:
SimpleDateFormate的parse()方法会用到它爹申明的一个属性calendar。多线程操作同一个calendar对象的时候就可能会产生线程安全问题
可能会产生问题的代码
try {
// 源码大概在SimpleDateFormat类的1532行处
// 这里是将calb中的处理好的时间信息传递到calendar中
parsedDate = calb.establish(calendar).getTime();
...
...
}
让我们走进establish( )的内心世界看一下
//源码在CalendarBuilder类的103处
...
//这里先进行了一个clear()操作
cal.clear();
//如下进行了一个赋值操作
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;
}
}
}
if (weekDate) {
int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
int dayOfWeek = isSet(DAY_OF_WEEK) ?
field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
if (dayOfWeek >= 8) {
dayOfWeek--;
weekOfYear += dayOfWeek / 7;
dayOfWeek = (dayOfWeek % 7) + 1;
} else {
while (dayOfWeek <= 0) {
dayOfWeek += 7;
weekOfYear--;
}
}
dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
}
cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);
}
//这里是将处理完了calendar返回
return cal;
这个calendar对象在当前方法中被清理并被重新赋值,在多线程的环境下,如果A线程clear()完毕后正在对calendar对象进行重新赋值。
就是此时,啪!B线程跑进来了,给calendar又clear()了!这不就GG了嘛!
上面提到的这个calendar来自SimpleDateFormat的父类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;
...
}
public class SimpleDateFormat extends DateFormat {
...
}
当前对于SimpleDateFormat的线程不安全问题其实有众多的解决方法,比如
- 一个线程给NEW 一个对象(强烈不推荐的咯)
- 加锁!(例如synchronized,咱就是说,影响程序执行效率,其实也不是很推荐)
- ThreadLocal来处理(等KK弄明白了这玩意再来扯犊子🤩)
- 和我一样使用DateTimeFormatter类👀
结束
散会!