SimpleDateFormat是线程不安全的

概述

最近在搞大数据实时计算,期间在解析时经常会用到 SimpleDateFormat对时间进行格式化。通过观察每天的日志经常会发现下列异常:

java.lang.NumberFormatException: For input string: ""
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
        at java.lang.Long.parseLong(Long.java:431)
        at java.lang.Long.parseLong(Long.java:468)
        at java.text.DigitList.getLong(DigitList.java:177)
        at java.text.DecimalFormat.parse(DecimalFormat.java:1297)
        at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)
        at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311)
        at java.text.DateFormat.parse(DateFormat.java:335)
        at com.xxxxxxx.core.common.util.DateUtil.parseTimestamp(DateUtil.java:95)
        at com.xxxxxxx.core.common.util.DateUtil.parse(DateUtil.java:84)
        at com.xxxxxxx.hbase.generator.LogRowKeyGenerator.generate(LogRowKeyGenerator.java:21)
        ... 22 more

 

 原因

通过查找资料发现SimpleDateFormat是继承自DateFormat类,该类中直接使用了Calendar成员变量,该变量在多线程中共享,是线程不安全的,关于线程安全可以参考这里

protected Calendar calendar;

而我们实际中经常在常量类中定义一个如下的对象:

public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

 在多线程中使用DATE_FORMAT.parse(str)、DATE_FORMAT.format(str)就会遇到上述异常问题。

 究其原因,可以看下SimpleDateFormat源码:

public Date parse(String text, ParsePosition pos)
    {
    
        checkNegativeNumberExpression();
    
        int start = pos.index;
        int oldStart = start;
        int textLength = text.length();
        ........//省略
        //establish方法中会调用 calendar.clear(); 
        parsedDate = calb.establish(calendar).getTime();
        ........//省略

        //采用text 重新给calendar赋值
        start = subParse(text, start, tag, count, obeyCount,
				 ambiguousYear, pos, useFollowingMinusSignAsDelimiter);
        
        ........//省略
        Date parsedDate = calendar.getTime();
        return parsedDate 
    }

 在多线程高并发情况下,A线程执行完成calendar.clear(),但还没有执行getTime(), B线程又执行calendar.clear()方法,当A线程执行getTime()就会报上述异常java.lang.NumberFormatException: For input string: ""。

 

报空指针只是其中一种情况,还会有些未知的错误,就是两个线程中的日期串改。

 

解决办法

我的解决办法就是每次都new SimpleDateFormat()

在工具类中提供一个静态方法每次都new SimpleDateFormat():

public static DateFormat getSimpleDF(){
      return new SimpleDateFormat("yyyy-MM-dd");
}

也可以用ThreadLocal来解决在同一个线程中对 DateFormat进行复用,关于ThreadLocal可以参考这里

猜你喜欢

转载自moon-walker.iteye.com/blog/2352829