线程不安全的 SimpleDateFormat

JDK 中提供了 SimpleDateFormat 类,可以用来做日期格式化。这个类有一个令人超级蛋疼的问题:不是线程安全的!!!其实 JDK 文档中有说这件事,但是说的非常含蓄:_

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

我在写代码的时候不太喜欢仔细 API 文档,所以就被坑。下面是一个一定会出现并发问题的(代码是 Groovy 写的,比 Java 版简单,可以等价转成 Java):

  1. static void main(String[] ss) {
  2. def SDF = new SimpleDateFormat('yyyy-MM-dd')
  3. Thread.start {
  4. while (true) {
  5. def date = SDF.parse("2018-07-27")
  6. assert SDF.format(date) == "2018-07-27"
  7. }
  8. }
  9. Thread.start {
  10. while (true) {
  11. def date = SDF.parse("2016-12-03")
  12. assert SDF.format(date) == "2016-12-03"
  13. }
  14. }
  15. }

这段代码呢,你每次执行,都可能抛出不同的异常。真是很不爽。要想愉快的格式化,就要想点别的办法。

补救方法

解法一:朴素做法

每次都 new 一个新的 SimpleDateFormat。直觉告诉你这样做很浪费。这样做很简单,所以就不举例了。

解法二:使用 ThreadLocal

使用 ThreadLocal 就是文档“recommend”的每个线程创建一个单独实例,然后每次使用的时候去 ThreadLocal 中去取就好了。

static ThreadLocal<SimpleDateFormat> sdf
    = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"))

解法三:使用开源库

在 apache commons lang3 这个包中有一个叫 FastDateFormat 的类。它的文档的第一行就是:

FastDateFormat is a fast and thread-safe version of SimpleDateFormat.

看看,天下苦 SimpleDateFormat 久矣(搞不清楚 JDK 中的 SimpleDateFormat 为啥不改成线程安全的,难道担心有人使用它线程不安全的特性,改完后有兼容问题??)。使用这个类替代 SimpleDateFormat,全局只需要一个对象就够啦。