【Java 陷阱】SimpleDateFormat 时间格式化多线程异常

SimpleDateFormat 陷阱

SimpleDateFormat是java中常用的时间格式化工具,除了转化时间格式,还可以解析字符串到时间类型,方便好用,但是陷阱就在其中。如果你遇到类似看到时间字符串完全正确,但就是不能正确格式化,或者明明时间类型没错,但就是无法转化为其他格式,亦或是格式转化错误随机出现,那想想,是不是陷入了多线程并发的陷阱。

调用:

private static SimpleDateFormat sdf = new SimpleDataFormat("yyyy-MM-dd HH:mm:ss");
...
...

public static Date parseDateFromString(String str){
     try{
         return sdf.parse(str);
     }catch(Exception e){
         //TODO
     }
}
...
...

多线程情况下数据随机抛出异常:

java.lang.NumberFormatException: multiple points
        at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1082)
        at java.lang.Double.parseDouble(Double.java:510)
        at java.text.DigitList.getDouble(DigitList.java:151)
        at java.text.DecimalFormat.parse(DecimalFormat.java:1302)
        at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1934)
        at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311)
        at java.text.DateFormat.parse(DateFormat.java:335)

这种问题在单机情况下调试难以发现问题,根本原因在于SimpleDateFormat本身不是线程安全的:

SimpleDateFormat.java

 *** Date formats are not synchronized.**
 * It is recommended to create separate format instances for each thread.
 * If multiple threads access a format concurrently, it must be synchronized
 * externally.
 *
 * @see          "http://java.sun.com/docs/books/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial
 * @see          java.util.Calendar
 * @see          java.util.TimeZone
 * @see          DateFormat
 * @see          DateFormatSymbols
 * @version      1.88, 05/28/08
 * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
 */
public class SimpleDateFormat extends DateFormat {
**
..
protected Calendar calendar;
..
    // Called from Format after creating a FieldDelegate
        private StringBuffer format(Date date, StringBuffer toAppendTo,
                                    FieldDelegate delegate) {
            // Convert input date to time field list
            calendar.setTime(date);
    .....
    }
    public Date parse(String text, ParsePosition pos)
    {
        int start = pos.index;
        int oldStart = start;
    int textLength = text.length();
    calendar.clear(); // Clears all the time fields
    ...
    ...
    }
}

从上面代码片段可以看到,多线程下,对象calendar会被频繁的修改,从而导致不可预知的错误。

解决方案:

  1. SimpleDateFormat 实例化在方法内部,每次重新定义变量
    不好,导致format对象频繁创建,大并发下影响性能

  2. 同步代码块,对sdf共享变量加锁 synchronize(sdf)
    不好,并发下加锁会降低系统的并发性能,极限情况下退化为单线程,不推荐

  3. 使用ThreadLocal,隔离线程共享变量
    推荐使用,类似如下:

      private static ThreadLocal threadLocal = new ThreadLocal() {
      @Override
      protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    public static Date parse(String dateStr) throws ParseException {
        return threadLocal.get().parse(dateStr);
    }public static String format(Date date) {
        return threadLocal.get().format(date);
    }

结束

你可能感兴趣的:(编程心得)