为什么SimpleDateFormat不是线程安全的
我们用一个简单的例子来说明:
1 | import java.text.DateFormat; |
基于是多线程程序,线程运行的先后不可预知。但是奇怪的事情发生了,连可预知的FORMAT.parse("2016-01-01")
也出现了奇怪的结果。
以下是几个可能出现的错误结果:
错误结果1
1 | Created Thread 2, Wed Jan 01 00:00:00 HKT 2200 |
错误结果2
1 | Created Thread 1, Sat May 26 09:34:08 HKT 164390675 |
错误结果3
1 | java.lang.NumberFormatException: For input string: ".11E.111E" |
这正是因为SimpleDateFormat是非线程安全所导致的,这应该是设计者的错误。但是基于向后兼容,JDK中一直没有将它改为线程安全。然后Java 8引入了新的类解决了这一问题,下面会进行详细阐述。
从SimpleteDateFormat的源代码中可以知道,parse(..)
的时候,首先会calender.clear()
,然后会calender.set(..)
。如果一个线程parse(..)
还没有返回,另一个线程也进入了parse(..)
并进行了calender.clear()
,那么第一个线程将会得到意想不到的结果。
所以在SimpleDateFormat的文档中,也做了说明:
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.
如何写出线程安全的date formatter
针对写出线程安全的date formatter,在Java 8之前主要有三种方法:
- 每个线程使用单独的DateFormat实例
- 使用synchronized进行同步
- 使用ThreadLocal
每个线程使用单独的DateFormat实例
在每个线程里,创建一个新的DateFormat实例。
1 | import java.text.DateFormat; |
这种方法虽然实现起来很简单,但是效率非常的低。
使用synchronized进行同步
1 | import java.text.DateFormat; |
使用ThreadLocal
1 | import java.text.DateFormat; |
以上三种方法的性能测试
有人针对以上的三种方法进行了性能测试,性能排序为:
使用ThreadLocal > 使用synchronized进行同步 > 每个线程使用单独的DateFormat实例
但是需要指出的是ThreadLocal的性能是在有线程池的情况下,假如没有线程池,和每个线程单独new一个DateFormat实例是没有区别的
所以我建议,多线程环境下,最好不用使用SimpleDateFormat,无论哪种方法都会写一些多余的代码,性能也不见得好。
线程安全的Date Formatter
在Java 8之前的Java版本,我们可以考虑使用线程安全的类
- apache commons中的FastDateFormat
- joda-time中的DateTimeFormat
Java 8中的DateTimeFormatter
Java 8中引入了新的包java.time
,完美的解决了线程不安全的问题。
将字符串转换成java.time.LocalDate
:
1 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |