为什么SimpleDateFormat线程不安全?

为什么SimpleDateFormat线程不安全?

SimpleDateFormat中有个属性calendar(protected Calendar calendar;)继承自DataFormat

SimpleDateFormat的format方法中将传入的时间date set给了calendar属性(calendar.setTime(date);),所以当不同的线程如果用的是同一个SimpleDateFormat对象,则calendar对象也是完全相同的内存;

当A线程传入时间a调用format方法进行格式化,format方法还没执行完,此时B线程传入时间b调用format方法进行格式化,则A线程中的calendar的time就变成了B线程传入的时间b了,当执行format方法中的subFormat方法时,获取calendar中的时间就线程不安全了。

java.text.SimpleDateFormat#format(java.util.Date, java.lang.StringBuffer, java.text.Format.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; ) {
        // 省略部分代码……
        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;
}

java.text.SimpleDateFormat#subFormat 源码:

private void subFormat(int patternCharIndex, int count,
                       FieldDelegate delegate, StringBuffer buffer,
                       boolean useDateFormatSymbols)
{
    int     maxIntCount = Integer.MAX_VALUE;
    String  current = null;
    int     beginOffset = buffer.length();

    int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
    int value;
    if (field == CalendarBuilder.WEEK_YEAR) {
        if (calendar.isWeekDateSupported()) {
            value = calendar.getWeekYear();
        } else {
            // use calendar year 'y' instead
            patternCharIndex = PATTERN_YEAR;
            field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
            value = calendar.get(field);
        }
    } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
        value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
    }
      // 省略部分代码…… 

解决方法

  1. 不同线程使用不同的SimpleDateFormat对象,每次new一个新的出来或者使用ThreadLocal,缺点消耗内存资源
  2. 要执行SimpleDateFormat之前进行加锁,缺点效率不高
  3. 使用其他时间类比如Java8线程安全DateTimeFormatter,缺点可能是要重构老代码等

猜你喜欢

转载自www.cnblogs.com/theRhyme/p/12395553.html