0. 背景
有时候,在项目中为了避免过多创建对象,会把SimpleDateFormat的对象声明为静态的。但是这样却会导致在多线程的环境中出问题。
一般的用法是生命一个静态的SimpleDateFormat对象,然后提供一个工具方法来调用它的 format 方法,在多线程的环境下,就会出现一些意想不到的问题。
1 |
|
1. 原因分析如下:
SimpleDateFormat 的 format 方法的源码如下:
1 | // Called from Format after creating a FieldDelegate |
可以看到内部的实现是依赖 Calendar 的 setTime 方法的:calendar.setTime(date) 。接下里,就涉及到 Calendar 类的线程安全问题了,但是很不幸,Calendar 的实现也是线程不安全的,它内部的实现原理是通过保存了一个从Unix纪元开始的时间偏移量来定位时间的,而且关键的是它是可变的,通过 setTime 方法可以改变它内部的时间。
1 |
|
这样的话,问题的原因就清楚了,在多个线程共享该SimpleDateFormat 对象的时候,每一个线程内部都会调用 setTime 来改变这个共享对象,进而出现不可预测的后果。
2. 解决方案
有了前文的分析后,要解决这个问题,其实就是需要解决format 方法的线程同步问题,这就是一个简单的问题了,大致有以下方案可以解决:
- 把静态方法的调用为线程安全的,比如加上锁:
1 | // 日期格式化工具类 |
- 使用ThreadLocal 对象保证 SimpleDateFormat 对象对于每一个线程都是有一个对应的变量的,由于SimpleDateFormat 中的 Caldendar 对象都是随着 SimpleDateFormat 对象的创建而创建的(调用Calendar.getInstance()实现,这个方法会触发创建新的 Calendar 对象),这样就能保证每一个线程中有一个唯一的 Calendar 对象。
1 | // 日期格式化工具类 |
参考资料:
https://zhuanlan.zhihu.com/p/59900271
https://bbs.csdn.net/topics/290031298