为什么多线程下不建议使用SimpleDateFormat

前言

经常听到开发大佬说不建议用SimpleDateFormat,包括阿里巴巴java开发手册也写了“SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。”


v2-7685d0e9916fe379a2f40c59e2bfde66_b.jpg


可是,为什么说SimpleDateFormat是线程不安全的类呢?要想证明这个问题,首先要聚一个例子,看看在多线程环境下,使用SimpleDateFormat会怎么样。

public static void main(String[] args) {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    CountDownLatch   countDownLatch   = new CountDownLatch(30);
    for (int i = 0; i < 30; i++) {
        CompletableFuture.runAsync(() -> {
            //先获取当前时间的格式化字符串
            String str1 = simpleDateFormat.format(new Date());
            try {
                //在通过格式化字符串再操作一次
                Date   parseDate = simpleDateFormat.parse(str1);
                String str2      = simpleDateFormat.format(parseDate);
                //对比前后格式化字符串是否一致
                System.out.println(String.format("threadId = %s , 前 = %s , 后 = %s", Thread.currentThread().getId(), str1, str2));
            } catch (Exception ignored) {
            } finally {
                countDownLatch.countDown();
            }
        }, ThreadPoolUtil.pool);
    }
    countDownLatch.await();
}

在同个一个线程中,多次操作同一个时间new Date(),比较前后操作的结果是否一样。一样,则代表是现场安全的。
在看一下运行的结果


v2-56078882d6e0d4fff697ac28a8c6f928_b.jpg



发现确实在某些线程中,前后的时间不一致。那就说明SimpleDateFormat确实是线程不安全的。
看一下format方法的源码

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,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。
此外,parse方法也是线程不安全的,parse方法实际调用的是CalenderBuilder的establish来进行解析,其方法中主要步骤不是原子操作。 想要避免,要不就加锁、要不就在方法内new SimpleDateFormat。不管是哪种,都是对性能有所影响的。JDK8的DateTimeFormatter是一个不错的解决方式。我们来看看DateTimeFormatter是怎么保证线程安全的。

public final class DateTimeFormatter {

    /**
     * The printer and/or parser to use, not null.
     */
    private final CompositePrinterParser printerParser;
    /**
     * The locale to use for formatting, not null.
     */
    private final Locale locale;
    /**
     * The symbols to use for formatting, not null.
     */
    private final DecimalStyle decimalStyle;
    /**
     * The resolver style to use, not null.
     */
    private final ResolverStyle resolverStyle;
    /**
     * The fields to use in resolving, null for all fields.
     */
    private final Set<TemporalField> resolverFields;
    /**
     * The chronology to use for formatting, null for no override.
     */
    private final Chronology chrono;
    /**
     * The zone to use for formatting, null for no override.
     */
    private final ZoneId zone;

DateTimeFormatter的类是用final修饰的,成员变量也是用final修饰的。这就代表DateTimeFormatter是个不可变的对象,意味着线程B对象无法修改线程A的DateTimeFormatter对象中的变量。线程B只能自己新建一个DateTimeFormatter对象。

总结

简述了SimpleDateFormat为什么是线程不安全的和DateTimeFormatter为什么是线程安全的之后,我们在实际开发中,要努力去规范我们的代码,在使用封装好的工具类时,也要知其原理,避免问题。
如果项目是JDK8的话,在处理时间问题时,不妨直接用DateTimeFormatter来试试!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值