非线程安全类SimpleDateFormat

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/matthew_zhang/article/details/62082957

SimpleDateFormat是非线程安全的,写处理日期的工具类时候请注意。

问题背景:

项目组的同事在新项目里写了一个DateUtil专门处理日期格式化的工具。线上运行后台日志偶然发生莫名其妙的错误:

java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string: “”
java.lang.NumberFormatException: For input string: “.31023102EE22”

例如:

java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1110)
    at java.lang.Double.parseDouble(Double.java:540)
    at java.text.DigitList.getDouble(DigitList.java:168)
    at java.text.DecimalFormat.parse(DecimalFormat.java:1321)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455)
    at java.text.DateFormat.parse(DateFormat.java:355)

或者

java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:453)
    at java.lang.Long.parseLong(Long.java:483)
    at java.text.DigitList.getLong(DigitList.java:194)
    at java.text.DecimalFormat.parse(DecimalFormat.java:1316)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455)
    at java.text.DateFormat.parse(DateFormat.java:355)

原因分析:

根据错误日志搜来问题代码:

public class DateUtil {

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
   ...
   ...
    public static Date parse(String strDate) throws ParseException{
        return sdf.parse(strDate);
    }
}

再分析项目调用该代码的场景:

Service A (线程1)某方法执行DateUtil.parse(“2017-02-12”)

Service B (线程2)某方法执行DateUtil.parse(“2017-03-12”)

并且当service A和service B同时触发上面代码时候就出问题了。


JDK源码分析:

调用链:
SimpleDateFormat里parse(String strDate)
=>DateFormat里parse(source, pos);

public class SimpleDateFormat extends DateFormat {
 ...
 transient private char[] compiledPattern;
 ...
 ...
 public Date parse(String text, ParsePosition pos)
    {
        checkNegativeNumberExpression();

        int start = pos.index;
        int oldStart = start;
        int textLength = text.length();

        boolean[] ambiguousYear = {false};

        CalendarBuilder calb = new CalendarBuilder();

        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            } ...
 ...
 ...

在DateUtil里变量SimpleDateFormat被定义为static,因而所有线程调用DateUtil时候都共享了该变量。
一看源码就直觉知道SimpleDateFormat是一个有状态的对象了,因为它拥有很多成员变量,而且变量和很多方法都没有加锁同步处理。
例如状态变量compiledPattern>>>8这句,假设多个线程同时修改该方法值,那各个线程间就互相影响了,从而SimpleDateFormat的parse方法肯定出问题。


解决方案:

  • 去掉全局静态变量SimpleDateFormat,在每个parse方法里new SimpleDateFormat
public class DateUtil {

   ...
   ...
    public static Date parse(String strDate) throws ParseException{
     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
     return sdf.parse(strDate);
    }
}
  • 在parse方法前加synchronized同步
public class DateUtil {
   private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
   ...
   ...
    public static synchronized Date parse(String strDate) throws ParseException{
     return sdf.parse(strDate);
    }
}
  • 使用线程封闭的ThreadLocal实现同一线程内共享,不同线程间隔离 (此方法不推荐,详细解释留意下一篇文章详解ThreadLocal)

猜你喜欢

转载自blog.csdn.net/matthew_zhang/article/details/62082957