SimpleDateFormat 的问题
在 Java 中,格式化日期通常使用 SimpleDateFormat 这个类(JDK提供的)。
我们知道,SimpleDateFormat 是线程不安全的,SimpleDateFormat在对时间进行格式化的方法format中,会先对calendar对象进行setTime的赋值,若是有多个线程同时操作一个SimpleDateFormat实例的话,就会对calendar的赋值进行覆盖,进而产生问题。。
1 | public final String format(Date date) { |
calendar.setTime(date)这条语句改变了calendar,稍后,calendar还会用到(在subFormat方法里),而这就是引发问题的根源。想象一下,在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:
- 线程1调用format方法,改变了calendar这个字段。
- 中断来了。
- 线程2开始执行,它也改变了calendar。
- 又中断了。
- 线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。如果多个线程同时争抢calendar对象,则会出现各种问题,时间不对,线程挂死等等。
分析一下format的实现,我们不难发现,用到成员变量calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
这个问题背后隐藏着一个更为重要的问题–无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的
所以,大部分时候则是在方法内部 new 出新的 DateFormat 对象再做格式化,如:DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
。但在高访问量的情况下,频繁创建实例也会导致内存开销大和 GC 频繁问题。
FastDateFormat
Apache 的 commons-lang 包下有个 FastDateFormat 可以方便的解决上述问题。
查看其源码:
1 | /*FastDatePrinter#format |
在新的版本(3.0 以上)中 getInstance 方法内更加使用 ConcurrentMap 做缓存提高并发性能。
1 | // 3.0 版本源码 |
DateFormatUtils
基于上面 FastDateFormat 类,commons-lang 包还提供了 DateFormatUtils 工具类,提供了两方面的功能:
- 提供静态的 format 方法,实现一行代码格式化日期。
- 内部初始化了一些 final 的 FastDateFormat,方便开发者直接调用。