SimpleDateFormat线程安全问题排查( 二 )

不仅有的线程结果不正确,甚至还有一些线程还出现了异常!

  1. 为什么SimpleDateFormat类不是线程安全的?
SimpleDateFormat继承了DateFormat , DateFormat内部有一个Calendar对象的引用,主要用来存储和SimpleDateFormat相关的日期信息 。
SimpleDateFormat对parse()方法的实现 。关键代码如下:
@Overridepublic Date parse(String text, ParsePosition pos) {...省略中间代码Date parsedDate;try {...parsedDate = calb.establish(calendar).getTime();} catch (IllegalArgumentException e) {...}return parsedDate;}establish()的实现如下:
Calendar establish(Calendar cal) {...省略中间代码cal.clear();for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {for (int index = 0; index <= maxFieldIndex; index++) {if (field[index] == stamp) {cal.set(index, field[MAX_FIELD + index]);break;}}}...return cal;}在多个线程共享SimpleDateFormat时,同时也共享了Calendar引用 , 在如上代码中,calendar首先会进行clear()操作 , 然后进行set操作,在多线程情况下,set操作会覆盖之前的值,而且在后续对日期进行操作时,也可能会因为clear操作被清除导致异常 。
四. 解决方案
  1. 将SimpleDateFormat定义成局部变量,每次使用时都new一个新对象,频繁创建对象消耗大,性能影响一些(JDK文档推荐此做法)
public static Date parse(String strDate) throws ParseException {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf.parse(strDate);}
  1. 维护一个SimpleDateFormat实体,转换方法上使用 Synchronized 保证线程安全:多线程堵塞(并发大系统不推荐)
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static String formatDate(Date date)throws ParseException{synchronized(sdf){return sdf.format(date);}}public static Date parse(String strDate) throws ParseException{synchronized(sdf){return sdf.parse(strDate);}}
  1. 使用ThreadLocal : 线程独享不堵塞,并且减少创建对象的开销(如果对性能要求比较高的情况,推荐这种方式) 。
public static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static Date parse(String strDate) throws ParseException {return threadLocal.get().parse(strDate);}
  1. DateTimeFormatter是Java8提供的新的日期时间API中的类,DateTimeFormatter类是线程安全的,可以在高并发场景下直接使用 。
String dateTimeStr= "2016-10-25 12:00:00";DateTimeFormatter formatter02 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr,formatter02);System.out.println(localDateTime);String format = localDateTime.format(formatter02);System.out.println(format);2016-10-25T12:002016-10-25 12:00:00最终,我们根据实际情况公共包DateUtil类提供的strConvertDate方法 , 原理是按照方案1来解决该问题 。
【SimpleDateFormat线程安全问题排查】

推荐阅读