系统中对文档的处理有这么一个简单的过程
1.通过web service取出文档属性数据
2.对文档属性数据进行转化处理,其中有一项转化是把文档发布日期由一种显示格式转为另外一种显示格式,这个转化是不久前加上去的
3.根据文档发布日期对文档排序,将最新的文档放在前面
最近经常出现一种情况,经常会有非常旧的文档排在了前面,初步判断有两种可能性
1.排序算法有问题
2.日期格式有问题,没被正常解析
1个小时过去了,排序没问题,两个小时过去了,web service返回日期串是好的,但是发现一个奇怪的现象
日期2009-07-09 03:45:01 竟然被转化成了15 Mar 2015 03:45:01,并且这种现象貌似比较随机,大多说时候日期解析都是完全正确的2009-07-09 03:45:01 -> 09 Jul 2009 03:45:01
代码里挖呀挖,终于发现这么一行
public static final DateFormat DOCTIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
很明显,由于这个日期格式在很多地方会用到,写代码的为了统一也出于性能考虑创建了这么一个公有常量。而这么做是有代价的。
好了,切入正题。
我们经常会用SimpleDateFormat去做日期的解析和格式化,然而经常被忽略的一个事实是 SimpleDateFormat是非线程安全的。
且看下面一段代码,
public class DateFormatTest {
private static final DateFormat DOCTIME_FORMAT = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss");
/**
* @param args
*/
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(30);
for (int i=0; i<20; i++) {
Runnable task = new Runnable(){
@Override
public void run() {
Date date;
try {
date = DOCTIME_FORMAT.parse("07/11/2009 09:12:17");
System.out.println(DOCTIME_FORMAT.format(date));
} catch (ParseException e) {
// ignore
}
}
};
exec.execute(task);
}
}
}
我得到了这样的结果:
07/11/2009 09:12:17
07/01/1970 12:00:00
07/01/2009 12:12:17
07/11/2009 09:12:17
.....
本来这也不是什么大问题,非线程安全的类多了,问题是JDK1.4以前的文档里并没像说明HashMap一样说明该类是非线程安全的,JDK1.4以后的文档里最有有句说明,我们很多时候还是默认它是线程安全的了,因为我们用它的时候完全考虑的是怎样格式化或解析日期。这也提醒我们:
1.自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明
2.对线程环境下,对每一个共享的可变变量都要注意其线程安全性
问题找到了,得有方案不是,想来基本就下面几种办法:
1. 将
private static final DateFormat DOCTIME_FORMAT = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss");
改为
private static final String DOCTIME_FORMAT = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss");
在需要用到DateFormat的地方新建一个实例,不管什么时候,将有线程安全问题的对象由共享变为局部私有都能避免多线程问题,不过也加重了创建对象的负担。
2. 将DateFormat放在ThreadLocal中
private static final ThreadLocal<DateFormat> DOCTIME_FORMAT = new ThreadLocal<DateFormat>() {
protected DateFormat initialValue() {
return new SimpleDateFormat("MM/dd/yyyy hh:mm:ss");
}
};
使用ThreadLocal, 也是将共享变量变为独享,线程独享肯定能比方法独享并发环境能减少不少创建对象的开销
3. 使用Apache commons 里的FastDateFormat,宣称是既快又线程安全的SimpleDateFormat, 可惜它只能对日期进行format, 不能对日期串进行解析。
Over.