前言
SimpleDateFormat 是 JDK 提供的一个日期格式化和解析类,但它是非线程安全的,原因如下。
1. 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();
}
}
}
// An IllegalArgumentException will be thrown by Calendar.getTime()
// if any fields are out of range, e.g., MONTH == 17.
catch (IllegalArgumentException e) {
pos.errorIndex = start;
pos.index = oldStart;
return null;
}
其中,calb.establish(calendar)初始化了calendar日历数据
跟进查看
//1-----------------------清空
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]);//2--------------设置
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);//2-----------设置
}
//3-------------------返回
return cal;
参考1- 2- 3-注释,线程不安全的原因,不能保证多核模式下,多线程执行是顺序执行的原子性
2. format方法源码
/**
* Formats the given Date
into a date/time string and appends
* the result to the given StringBuffer
.
*
* @param date the date-time value to be formatted into a date-time string.
* @param toAppendTo where the new date-time text is to be appended.
* @param pos the formatting position. On input: an alignment field,
* if desired. On output: the offsets of the alignment field.
* @return the formatted date-time string.
* @exception NullPointerException if the given {@code date} is {@code null}.
*/
@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;
}
//设置日期
calendar.setTime(date);
//格式化时间
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
calendar是有状态的,不能保证原子性。
3. 解决方法
3.1. 加锁
自建方法包裹代码
public class SimpleDateFormatTest {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static synchronized Date parse(String s) throws ParseException {
return sdf.parse(s);
}
public static void main(String[] args) {
for (int i = 0; i <20 ; ++i) {
Thread thread = new Thread(() -> {
public void run() {
try {
System.out.println(parse("2018-07-17 10:52:16"));
} catch (ParseException e) {
e.printStackTrace();
}
}
};
thread.start()
}
}
}
串行执行,在大批量调用效率很低,不推荐
3.2.每次调用创建一个对象
public Date parse(String s) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(s);
}
缺点,耗内存,GC压力增大,每次创建对象销毁,分配和销毁内存空间,整理内存,GC。不推荐
3.3.每个线程创建一个对象
每个线程带一个ThreadLocalMap对象,以threadlocal对象为key,传递变量为值。每个线程仅修改当前线程的副本。
public class SimpleDateFormatTest {
static ThreadLocal local= new ThreadLocal(){
@Override
protected SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static void main(String[] args) {
for (int i = 0; i < 5; ++i) {
Thread thread = new Thread(() -> {
public void run() {
try {
System.out.println(local.get().parse("2018-07-17 11:01:16"));
} catch (ParseException e) {
e.printStackTrace();
}finally {
//TODO 线程运行结束清除,避免内存泄露
local.remove();
}
}
});
thread.start();
}
}
}
4. 总结
推荐使用ThreadLocal,每个线程一个对象,提高format对象利用率
注意:使用ThreadLocal的Thread均持有ThreadLocal自定义的map对象,key是ThreadLocal对象的弱引用,随ThreadLocal的GC或者内存不足就会GC,value是我们创建的对象是强引用,可能一直存在。因此ThreadLocal调用结束后,需要显示调用remove()方法