为啥线程不安全?
主要包含两大块 parse 和 format 不安全。
可以看到,多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。
SimpleDateFormat 的 format 方法线程不安全问题
public final String format(Date arg0) { return this.format(arg0, new StringBuffer(), DontCareFieldPosition.INSTANCE).toString(); }
this.format 使用的是 SimpleDateFormat 的format 方法
private StringBuffer format(Date arg0, StringBuffer arg1, FieldDelegate arg2) { this.calendar.setTime(arg0); boolean arg3 = this.useDateFormatSymbols(); int arg4 = 0; while (arg4 < this.compiledPattern.length) { int arg5 = this.compiledPattern[arg4] >>> 8; int arg6 = this.compiledPattern[arg4++] & 255; if (arg6 == 255) { arg6 = this.compiledPattern[arg4++] << 16; arg6 |= this.compiledPattern[arg4++]; } switch (arg5) { case 100 : arg1.append((char) arg6); break; case 101 : arg1.append(this.compiledPattern, arg4, arg6); arg4 += arg6; break; default : this.subFormat(arg5, arg6, arg2, arg1, arg3); } } return arg1; }
不同线程
this.calendar.setTime(arg0);
依然会导致线程不安全问题。
SimpleDateFormat是继承DateFormat类,DateFormat类中维护一个Calendar 对象
SimpleDateFormat 继承 DateFormat ,使用的calendar 是父类 DateFormat中的 public class SimpleDateFormat extends DateFormat {} DateFormat 的 calendar 被用来进行 日期-时间计算,也被用于 format 方法也被用于 parse方法 public abstract class DateFormat extends Format { protected Calendar calendar; }
Parse 导致的线程安全问题
SimpleDateFormat 的 parse 方法
public Date parse(String arg0, ParsePosition arg1) { this.checkNegativeNumberExpression(); int arg2 = arg1.index; int arg3 = arg2; int arg4 = arg0.length(); boolean[] arg5 = new boolean[]{false}; CalendarBuilder arg6 = new CalendarBuilder(); int arg7 = 0; label82 : while (arg7 < this.compiledPattern.length) { int arg8 = this.compiledPattern[arg7] >>> 8; int arg9 = this.compiledPattern[arg7++] & 255; if (arg9 == 255) { arg9 = this.compiledPattern[arg7++] << 16; arg9 |= this.compiledPattern[arg7++]; } switch (arg8) { case 100 : if (arg2 < arg4 && arg0.charAt(arg2) == (char) arg9) { ++arg2; break; } arg1.index = arg3; arg1.errorIndex = arg2; return null; case 101 : while (true) { if (arg9-- <= 0) { continue label82; } if (arg2 >= arg4 || arg0.charAt(arg2) != this.compiledPattern[arg7++]) { arg1.index = arg3; arg1.errorIndex = arg2; return null; } ++arg2; } default : boolean arg10 = false; boolean arg11 = false; if (arg7 < this.compiledPattern.length) { int arg12 = this.compiledPattern[arg7] >>> 8; if (arg12 != 100 && arg12 != 101) { arg10 = true; } if (this.hasFollowingMinusSign && (arg12 == 100 || arg12 == 101)) { int arg13; if (arg12 == 100) { arg13 = this.compiledPattern[arg7] & 255; } else { arg13 = this.compiledPattern[arg7 + 1]; } if (arg13 == this.minusSign) { arg11 = true; } } } arg2 = this.subParse(arg0, arg2, arg8, arg9, arg10, arg5, arg1, arg11, arg6); if (arg2 < 0) { arg1.index = arg3; return null; } } } arg1.index = arg2; try { Date arg15 = arg6.establish(this.calendar).getTime(); if (arg5[0] && arg15.before(this.defaultCenturyStart)) { arg15 = arg6.addYear(100).establish(this.calendar).getTime(); } return arg15; } catch (IllegalArgumentException arg14) { arg1.errorIndex = arg2; arg1.index = arg3; return null; } }
关键看
Date arg15 = arg6.establish(this.calendar).getTime();
这个里面 的 establish 方法。establish 是 CalendarBuilder 的方法
Calendar establish(Calendar arg0) { boolean arg1 = this.isSet(17) && this.field[17] > this.field[1]; if (arg1 && !arg0.isWeekDateSupported()) { if (!this.isSet(1)) { this.set(1, this.field[35]); } arg1 = false; } arg0.clear(); int arg2; int arg3; for (arg2 = 2; arg2 < this.nextStamp; ++arg2) { for (arg3 = 0; arg3 <= this.maxFieldIndex; ++arg3) { if (this.field[arg3] == arg2) { arg0.set(arg3, this.field[18 + arg3]); break; } } } if (arg1) { arg2 = this.isSet(3) ? this.field[21] : 1; arg3 = this.isSet(7) ? this.field[25] : arg0.getFirstDayOfWeek(); if (!isValidDayOfWeek(arg3) && arg0.isLenient()) { if (arg3 >= 8) { --arg3; arg2 += arg3 / 7; arg3 = arg3 % 7 + 1; } else { while (arg3 <= 0) { arg3 += 7; --arg2; } } arg3 = toCalendarDayOfWeek(arg3); } arg0.setWeekDate(this.field[35], arg2, arg3); } return arg0; }
主要看
arg0.clear();
这个会将 calendar 清除掉,并且没有设置新值
可知SimpleDateFormat维护的用于format和parse方法计算日期-时间的calendar被清空了,如果此时线程A将calendar清空且没有来得及设置新值,线程B也进入parse方法用到了SimpleDateFormat对象中的calendar对象,此时就会产生线程安全问题!
解决方案:
1、将SimpleDateFormat定义成局部变量
2、 加一把线程同步锁:synchronized(lock)
3、使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本
解决办法栗子:threadLocal
class ThreadLocalSimpleFormatDateUtil { private static final String date_format = "yyyy-MM-dd HH:mm:ss"; private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(); public static DateFormat getDateFormat() { DateFormat df = threadLocal.get(); if (df == null) { df = new SimpleDateFormat(date_format); threadLocal.set(df); } return df; } public static String formatDate(Date date) throws ParseException { return getDateFormat().format(date); } public static Date parse(String strDate) throws ParseException { return getDateFormat().parse(strDate); } }