非线程安全类SimpleDateFormat

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值