使 DateFormat 线程安全。使用什么,synchronized 还是 Thread local

Making DateFormat Threadsafe. What to use, synchronized or Thread local

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

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

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

最简单的解决方案是:

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

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

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

有多种选择,权衡取舍不同。

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

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

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

你可以

  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

避免遗留日期时间 classes

与最早版本的 Java 捆绑在一起的麻烦的旧日期时间 classes 已被 java.time classes 取代。 java.time class 是 thread-safe and use immutable objects.

java.time

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

如果需要,可以全局定义 DateTimeFormatter。 class 可以自动本地化正在生成的字符串,也可以指定某种格式。

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

Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds. The ZonedDateTime class 将 Instant 调整到特定时区。

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

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 framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

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

从哪里获得java.time classes?

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

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