微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JDK8 开始使用LcoalDateTime Insant DateTimeFormatter

SimpleDateFormat是线程非安全的

[强制] SimpleDateFormat 是线程不安全的类(主要是该类的方法非线程安全),一般不要定义为 static 变量,如果定义为 static , 必须加锁,或者使用 DateUtils 工具类。
正例: 注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
说明: 如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。

在《JAVA开发手册》中有提到上述的建议。我们就来详细解释下该建议的来龙去脉。

首先对SimpleDateFormat是非线程安全的进行一下解释。

最终调用代码如下:

    // 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)这句代码可以看出,SimpleDateFormat在format方法中将入参日期对象的时间set到calendar中calendar.setTime(date),calendar是全局变量,在SimpleDateFormat的多个方法中用到,一旦出现多线程调用的情况,calendar的值就会被修改,导致结果不正确甚至发生报错,所以SimpleDateFormat是线程不安全的.
 

DateTimeFormatter是线程安全的

首先来看一个DateTimeFormatter格式化的实例:

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(LocalDateTime.Now());
  • DateTimeFormatter#ofPattern

每次都会返回一个新的DateTimeFormatter对象

  • DateTimeFormatter#format
Formats a date-time object using this formatter.
This formats the date-time to a String using the rules of the formatter.

temporal  the temporal object to format, not null
the formatted string, not null
DateTimeException if an error occurs during formatting

public String format(TemporalAccessor temporal) {
      StringBuilder buf = new StringBuilder(32);
      formatTo(temporal, buf);
      return buf.toString();
}

  进入DateTimeFormatter#format函数的实现来看,其缓冲区每次调用都是新建的,进一步查看DateTimeFormatter#formatTo

  • DateTimeFormatter#formatTo

Formats a date-time object to an Appendable using this formatter.
     
This outputs the formatted date-time to the specified destination.
Appendable is a general purpose interface that is implemented by all
key character output classes including StringBuffer, StringBuilder,
PrintStream and Writer.
     
Although {@code Appendable} methods throw an {@code IOException}, this method does not.
Instead, any {@code IOException} is wrapped in a runtime exception.
     
temporal  the temporal object to format, not null
appendable  the appendable to format to, not null
DateTimeException if an error occurs during formatting
     
public void formatTo(TemporalAccessor temporal, Appendable appendable) {
    Objects.requireNonNull(temporal, "temporal");
    Objects.requireNonNull(appendable, "appendable");
    try {
        DateTimePrintContext context = new DateTimePrintContext(temporal, this);
        if (appendable instanceof StringBuilder) {
            printerParser.format(context, (StringBuilder) appendable);
        } else {
            // buffer output to avoid writing to appendable in case of error
            StringBuilder buf = new StringBuilder(32);
            printerParser.format(context, buf);
            appendable.append(buf);
          }
      } catch (IOException ex) {
          throw new DateTimeException(ex.getMessage(), ex);
       }
}

  在DateTimeFormatter#formatTo函数中重点注意DateTimePrintContext context = new DateTimePrintContext(temporal, this);这句代码表明传入的日期被封装到一个新的DateTimePrintContext对象中,所以你可以理解DateTimeFormatter#format函数就是一个简单的函数调用,并没有使用到共享数据;所以DateTimeFormatter#format是线程安全的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐