【问题标题】:Making DateFormat Threadsafe. What to use, synchronized or Thread local使 DateFormat 线程安全。使用什么,同步或线程本地
【发布时间】:2015-06-11 04:45:54
【问题描述】:

我想让以下代码线程安全。实现它的最佳方法是什么?

private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance();

public static final String eventTypeToDateTimeString(long timestamp)
{
   return DATE_FORMAT.format(new Date(timestamp));
}

【问题讨论】:

    标签: java multithreading thread-safety


    【解决方案1】:

    避免使用旧的日期时间类

    Java 最早版本捆绑的麻烦的旧日期时间类已被 java.time 类所取代。 java.time 类是thread-safe 并使用immutable objects

    java.time

    将您的格式化程序和日期类型替换为 java.time 类型以自动获得线程安全。

    如果需要,请在全局范围内定义您的 DateTimeFormatter。该类可以自动本地化正在生成的字符串,或者您可以指定某种格式。

    • 指定FormatStyle 以确定缩写的长度。
    • 指定Locale 以确定 (a) 用于翻译日期名称、月份名称等的人类语言,以及 (b) 决定缩写、大写、标点符号等问题的文化规范。
    • 为要调整时刻的时区指定ZoneId

    Instant 类代表UTC 中时间轴上的一个时刻,分辨率为nanosecondsZonedDateTime 类将 Instant 调整为特定时区。

    您的代码,翻译成 java.time 类。在实际工作中,我会将其分解为多行,并捕获异常。

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( Locale.CANADA_FRENCH ) ;
    private static final ZoneId ZONE_ID = ZoneId.of( "America/Montreal" );
    
    public static final String eventTypeToDateTimeString(long timestamp)
    {
       return Instant.ofEpochMilli( timestamp ).atZone( ZONE_ID ).format( DATE_TIME_FORMATTER );
    }
    

    不要将日期时间跟踪为从纪元开始计数

    我不建议传递 long 作为表示日期时间值的方式。使调试和记录变得困难,因为人类无法辨别日期时间值的含义。相反,传递 java.time 类型,例如 Instant。使用 java.time 类型提供了类型安全性并使您的代码更具自我记录性。


    关于java.time

    java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

    Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

    要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

    您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

    从哪里获得 java.time 类?

    ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

    【讨论】:

    • 旧课程一团糟——但新课程也是如此。它们没有正确实现 ISO8601,尽管它们提供了一些不同的半实现,不同重叠类之间的差距和行为各不相同。
    • @DanielWinterstein 我不明白你关于 ISO 8601 没有在 java.time 框架中正确实施的断言。我建议你提供细节,也许发布你自己的问题和答案来解决这些问题。如果您指的是 Leap Second 被吞下,我建议对于一般面向业务的应用程序来说这是合理的行为,保持日历与时钟同步。通过“停止时钟”或通过“smearing” 忽略跳跃是常见的做法,从而避免任何23:59:60
    • 您好 - 在某些情况下 java.time 无法解析有效且合理的 ISO8601 输入。有空的时候我会挖出来贴出来...
    • 有点晚了。 java.time 类不包括时间字符串的日期和时间部分之间的 T。 T 是 ISO 8601 的一部分。
    • @DwB 我不明白你的评论。你能澄清一下吗?年-月-日和时-分-秒部分之间的T 由ISO 8601 标准指定,是的。 java.time 类在解析或生成字符串时默认使用标准的ISO 8601 格式。要生成标准字符串,只需在各种 java.time 类上调用 toString。例如:调用Instant.now().toString() 获取标准字符串:2018-09-26T21:43:29.500Z as seen in IdeOne.com
    【解决方案2】:

    更好的是,使用:org.apache.commons.lang.time.FastDateFormat(是 SimpleDateFormat 的快速且线程安全的版本)

    【讨论】:

      【解决方案3】:

      有多种选择,各有不同的取舍。

      您可以同步访问单个 DateFormat。这最大限度地减少了创建的格式化程序对象的数量,但不同的线程必须在访问格式化程序之前竞争一个锁。这可能是性能最差的替代方案;很多线程最终可能会花费时间等待,并且您拥有的线程越多,情况就越糟糕。

      您可以为每次使用创建一个新的 DateFormat 对象。这将消除线程之间的争用,但如果有很多日期格式,您可能会使用这种方法对垃圾收集器施加压力,这会损害性能。但这在很多情况下都可以很好地工作,而且非常简单。

      第三种选择,使 DateFormat 成为线程局部变量,效率更高。线程之间没有争用,并且格式化程序可以被一个线程重复使用,所以它几乎不会产生那么多垃圾。缺点是它是最不直接的方法,如果你不清除它们,你放在 threadLocal 中的任何对象都可能比你想要的更长。

      【讨论】:

        【解决方案4】:

        你可以

        1. 每次需要时创建一个新的DateFormat 实例。

        2. 使用synchronized 块,正如@Giovanni Botta 所指出的那样。

        3. 使用ThreadLocal

          private static final ThreadLocal<DateFormat> THREADLOCAL_FORMAT =
              new ThreadLocal<DateFormat>() {
                  @Override protected DateFormat initialValue() {
                      return DateFormat.getDateTimeInstance();
                  }
              };
          
          public static final String eventTypeToDateTimeString(long timestamp) {
              return THREADLOCAL_FORMAT.get().format(new Date(timestamp));
          }
          

        实际上,如果你有一个线程池(意味着线程被重用),使用 ThreadLocal 可能会给你最好的性能,大多数 Web 容器都会这样做。

        参考

        http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html

        【讨论】:

          【解决方案5】:

          只需在每次调用时创建一个新副本,直到它实际上被证明是一个性能问题。手动管理线程本地的开销可能会淹没您从缓存它们中获得的任何优势。

          【讨论】:

            【解决方案6】:

            最简单的解决方案是:

            synchronized public static final String eventTypeToDateTimeString(long timestamp) {
              return DATE_FORMAT.format(new Date(timestamp));
            }
            

            无需使用ThreadLocals,因为这都是在静态上下文中。

            【讨论】:

            • 每种类型的同步都可能影响性能。但是您始终可以尝试不同的解决方案并比较对系统的影响。没有灵丹妙药。
            • @assylias OP 询问了同步问题,所以我只提到了最简单的解决方案,即通过同步重用同一个实例。
            • 这个答案的尝试显示了对线程安全和并发性在最基本和最根本的层面上的深刻无知。 不要按照这个“答案”的建议去做
            猜你喜欢
            • 2012-11-22
            • 2011-03-30
            • 1970-01-01
            • 2010-12-10
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多