FastDateFormat为什么能做到线程安全,而SimpleDateFormat不行?

后端 潘老师 1个月前 (03-24) 23 ℃ (0) 扫码查看

Java开发中,日期格式化是一个常见的操作。我们都知道SimpleDateFormat,但它存在线程不安全的问题。与之相比,FastDateFormat却能实现线程安全,这背后的原因是什么呢?

一、SimpleDateFormat为何线程不安全

SimpleDateFormat在多线程环境下是不安全的,这是很多开发者都清楚的事情。我们来看看它的内部代码,其中有一个protected Calendar calendar;成员变量。这意味着,当多个线程使用同一个SimpleDateFormat实例时,它们会共享这个calendar对象。
在进行日期格式化的过程中,就容易出现问题。下面是SimpleDateFormat的格式化方法部分代码:

private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // 如果第一个线程设置了时间之后还没有格式化为字符串,此时第二个线程将时间覆盖掉,就会出现线程安全问题
        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)设置时间后,还没来得及将其格式化为字符串,第二个线程可能就把时间给修改了,这样就会导致数据混乱,出现线程安全问题。

二、FastDateFormat实现线程安全的原理

(一)实例缓存机制

FastDateFormat之所以能做到线程安全,首先得益于它的缓存机制。在实例化FastDateFormat时,是通过缓存来获取实例的。相关代码如下:

private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
        @Override
        protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
            return new FastDateFormat(pattern, timeZone, locale);
        }
    };

public static FastDateFormat getInstance(final String pattern) {
    return cache.getInstance(pattern, null, null);
}

它把格式化格式、时区和国际化这些信息组合成一个键,存放在cInstanceCache这个ConcurrentHashMap中。也就是说,如果格式化格式、时区和国际化都相同,那么就会使用同一个FastDateFormat实例。下面这段代码展示了获取实例的过程:

final MultipartKey key = new MultipartKey(pattern, timeZone, locale);
F format = cInstanceCache.get(key);
if (format == null) {           
    format = createInstance(pattern, timeZone, locale);
    final F previousValue= cInstanceCache.putIfAbsent(key, format);
    if (previousValue != null) {
        // another thread snuck in and did the same work
        // we should return the instance that is in ConcurrentMap
        format= previousValue;              
    }
}

这种方式减少了重复创建实例的开销,同时也为线程安全提供了一定保障。

(二)局部变量的使用

除了缓存机制,FastDateFormat在格式化方法中也做了巧妙处理。来看它的格式化方法代码:

public String format(final Date date) {
    final Calendar c = newCalendar();
    c.setTime(date);
    return applyRulesToString(c);
}

在这个方法里,Calendar是局部变量。每个线程在调用format方法时,都会创建自己的Calendar对象,这样就避免了多个线程共享同一个Calendar对象带来的线程安全问题。

通过上述对SimpleDateFormat线程不安全原因的剖析,以及对FastDateFormat实现线程安全原理的讲解,我们可以清晰地看到两者的差异。在多线程环境下进行日期格式化操作时,FastDateFormat是一个更可靠的选择。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/16202.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】