SimpleDateFormat
是 Java 中非常常用的一个类,用于解析和格式化日期字符串,但是 SimpleDateFormat
在多线程环境中并不是线程安全的。
案例
|
|
运行结果报错,如下:
|
|
原因
在 JDK 的文档中提到了 SimpleDateFormat
的线程安全问题:
|
|
就是说DateFormat不是同步的,建议每个线程都分别创建format实例变量;或者如果多个线共享一个format的话,必须保持在使用format时是同步的.
源码分析
以SimpleDateFormat
类format()
方法为例:
|
|
在 format()
方法中先将日期存放到一个 Calendar
对象中,而这个 Calender
对象在 SimpleDateFormat
中还是以成员变量存在的。在随后调用 subFormat()
时会再次用到成员变量 calendar
。这就是引发问题的根源。在 parse()
方法中也会存在相应的问题。
在多线程环境下,如果两个线程都使用同一个 SimpleDateFormat
实例,那么就有可能存在其中一个线程修改了 calendar
后紧接着另一个线程也修改了 calendar
,那么随后第一个线程用到 calendar
时已经不是它所期待的值了。
SimpleDateFormat
其实是有状态的,它使用一个 Calendar
成员变量来保存状态;如果要求 SimpleDateFormat
的 parse()
和 format()
是线程安全的,那么它其实应该是无状态的。将 Calendar
对象作为局部变量,内部在进行方法调用时每次都把它作为参数进行传递,其实就应该可以做到线程安全了。JDK 中 SimpleDateFormat
的实现之所以没有这样做可能是出于性能上的考虑,可以节约每次方法调用时都要创建 Calendar
对象的开销。但这种有状态的设计在某些场景下却反而带来了使用上的不便。
解决方案
创建局部变量
|
|
SimpleDateFormat
进行加锁
|
|
使用 ThreadLocal
|
|
使用 Joda-Time(推荐)
Joda-Time 是一个很棒的开源的 JDK 的日期和日历 API 的替代品,其 DateTimeFormat 是线程安全而且不变的。
|
|