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