SimpleDateFormat

2019-02-18  本文已影响0人  康俊1024

一、概述
SimpleDateFormat被大量使用于处理时间格式化过程,由于该类在创建时会指定一个pattern用于标明固定的时间格式,所以在使用中,一般会创建一个作用域较大(static修饰或某类的私有属性)的对象用于重复使用。由于时间转换过程遇到的多线程并发的使用场景并不多见,所以很难发现在该类的隐患,事实上,该类并非是线程安全的,在多线程使用format()和parse()方法时可能会遇到问题。
二、分析
在SimpleDateFormat及其父类DateFormat的源文件里,有这样一段说明:
JDK文档中已经明确指出,这两个类在进行时间格式化的过程中都是非线程安全的。也就是说,使用同一个SimpleDateFormat实例,开若干线程做日期转换操作,得到的结果可能并不准确。

protected final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

需要明确的是,待转换的字符串作为非静态私有变量是每个对象持有的,只有sdf本身是公用的,不难发现即便是成功输出了,但是数值也未必会是正确的,parse()方法不安全。
format源码:

private StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate) {

  // Convert input date to time field list

  calendar.setTime(date);
}

需要注意的是calendar的操作并非是线程安全的,很显然在并发情景下,format的使用并不安全。
三、解决
既然SimpleDateFormat本身并不安全,那么解决的方式无非两种:优化使用过程或者找替代品。
1.临时创建
不使用Static,每次使用时,创建新实例。
存在的问题:
SimpleDateFormat中使用了Calendar对象,由于该对象相当重,在高并发的情况下会大量的new SimpleDateFormat以及销毁SimpleDateFormat,极其耗费资源。
2.synchronized
以synchronized同步SimpleDateFormat对象。
存在的问题:
高并发时,使用该对象会出现阻塞,当前使用者使用时,其他使用者等待,尽管结果是对的,但是并发成了排队,实际上并没有解决问题,还会对性能以及效率造成影响。
3.ThreadLocal
使用ThreadLocal,令每个线程创建一个当前线程的SimpleDateFormat的实例对象。
存在的问题:
使用ThreadLocal时,如果执行原子任务的过程是每一个线程执行一个任务,那么这样的声明基本和每次使用前创建实例对象是没区别的;如果使用的是多线程加任务队列,举个例子,tomcat有m个处理线程,外部有n个待处理任务请求,那么当执行n个任务时,其实只会创建m个SimpleDateFormat实例,对于单一的处理线程,执行任务是有序的,所以对于当前线程而言,不存在并发。
4.Apache的 DateFormatUtils 与 FastDateFormat
使用org.apache.commons.lang.time.FastDateFormat 与 org.apache.commons.lang.time.DateFormatUtils。
存在的问题:
apache保证是线程安全的,并且更高效。但是DateFormatUtils与FastDateFormat这两个类中只有format()方法,所有的format方法只接受long,Date,Calendar类型的输入,转换成时间串,目前不存在parse()方法,可由时间字符串转换为时间对象。
5.Joda-Time
使用Joda-Time类库。
四、问题底层
这里会导致的问题就是, 如果 线程A 调用了 sdf.parse(), 并且进行了 calendar.clear()后还未执行calendar.getTime()的时候,线程B又调用了sdf.parse(), 这时候线程B也执行了calendar.clear()方法, 这样就导致线程A的的calendar数据被清空了(实际上A,B的同时被清空了). 又或者当 A 执行了calendar.clear() 后被挂起, 这时候B开始调用sdf.parse()并顺利i结束, 这样 A 的 calendar内存储的的date 变成了后来B设置的calendar的date

上一篇下一篇

猜你喜欢

热点阅读