问题

java 程序都常用SimpleDateFormat 和 DateFormat 工具类,用来字符串和时间对象的相互转换,多线程环境下共享使用SimpleDateFormat 时,format() 和 parese() 方法会出下面出错。
实际开发中遇到的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Long.parseLong(Unknown Source)
at java.lang.Long.parseLong(Unknown Source)
at java.text.DigitList.getLong(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)
...
at java.lang.Thread.run(Unknown Source)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at java.lang.Double.parseDouble(Unknown Source)
at java.text.DigitList.getDouble(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)

原因

主要原因是 SimpleDateFormat 和 DateFormat 是线程不安全导致的。

解决

解决方法有很多,但是效率、简洁、美观上我推荐最后一个方法。对每个方法使用SimpleDateFormat对象时新建对象,使用局部变量解决问题,但是创建对象空间开销较大,不推荐。还有就是使用同步代码块synchronized,把 parse() 和 format() 分别同步起来,这个可以采用,但是总觉得想到同步就会想到慢,所以我没采纳。

使用ThreadLocal, 也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。

使用线程局部变量 ThreadLocal 来解决,以下是我采用的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 很重要
*
* SimpleDateFormat 和 DateFormat 不是线程安全,需要加锁,或者设置为局部线程安全
*/
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};

public static Date parse(String dateStr) {
try {
return threadLocal.get().parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}

public static String format(Date date) {
return threadLocal.get().format(date);
}

完结,希望能帮助你!