制作DateFormat Threadsafe。 使用什么,同步或线程本地

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

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

避免使用旧版日期时间类

与最早版本的Java捆绑在一起的麻烦的旧日期时间类已被java.time类取代。 java.time类是线程安全的并且使用不可变对象 。

java.time

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

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

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

Instant类表示UTC时间轴上的一个时刻,分辨率为纳秒 。 ZonedDateTime类将Instant调整为特定时区。

您的代码,转换为java.time类。 在实际工作中,我会将其分解为多行,并将陷入exception。

 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及更高版本中。 这些类取代了麻烦的旧遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

现在处于维护模式的Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参阅Oracle教程 。 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310 。

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

从哪里获取java.time类?

  • Java SE 8Java SE 9Java SE 10Java SE 11及更高版本 – 带有捆绑实现的标准Java API的一部分。
    • Java 9增加了一些小function和修复。
  • Java SE 6Java SE 7
    • 许多java.timefunction都被反向移植到ThreeTen-Backport中的 Java 6和7。
  • Android的
    • 更高版本的Android捆绑java.time类的实现。
    • 对于早期的Android(<26), ThreeTenABP项目采用ThreeTen-Backport (如上所述)。 请参见如何使用ThreeTenABP ….

ThreeTen-Extra项目使用其他类扩展了java.time。 该项目是未来可能添加到java.time的试验场。 您可以在这里找到一些有用的课程,如IntervalYearWeekYearQuarter等。

有几种选择,有不同的权衡。

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

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

第三种方法是使DateFormat成为一个threadlocal变量,效率要高得多。 线程之间没有争用,并且格式化程序可以被线程重复使用,因此它不会产生太多垃圾。 缺点是它是最简单的方法,如果你不清除它们,你放在threadLocal中的任何对象可能会比你想要的更长。

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

您可以

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

  2. 使用@Giovanni Botta指出的synchronized块。

  3. 使用ThreadLocal

     private static final ThreadLocal THREADLOCAL_FORMAT = new ThreadLocal() { @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

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

最简单的解决方案是:

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

不需要使用ThreadLocal因为这都是在静态上下文中。