jdk中SimpleDateFormat的实例线程不安全

3月 1st, 2016 1,784 留下评论 阅读评论

线上服碰到诡异问题,解析一段无参数固定代码生成的时间字符串获得时间戳,不定概率出现解析异常,堆栈如下。

产生的原因,因为SimpleDateFormat每次new的代价比较高,固定new一个后重复使用。一开始并没有对输入字符串进行日志打印,就以为是输入字符串的错误,就难以定位到产生问题的原因。

[ERROR]2016-03-01 09:03:13,446, [Class]DateTimeUtils, getTimestampFromDateString error : 2013-02-18 09:43:18.0
java.lang.NumberFormatException: For input string: "E.179E11"
    ...
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312)
    at java.text.DateFormat.parse(DateFormat.java:335)
    at com.netease.popo.common.util.DateTimeUtils.getTimestampFromDateString(DateTimeUtils.java)
    ...

明明参数传入的字符串是2013-02-18 09:43:18.0,怎么报异常就变成了E.179E11(有时候报错是空字符串,有时候提示multiple points),就很容易联想到是多线程引起的问题了。

解决的办法:

  1. 每次new SimpleDateFormat(dateTimeFormat).parse(time),代价比较大
  2. ThreadLocal,给每个线程新建一个SimpleDateFormat对象
  3. 线程安全的第三方库,比如joda-time(org.joda.time)或date4j(www.date4j.net),前者使用更广泛
  • 考虑到使用ThreadLocal需要自己管理且JDK对于日期时间API的弱势,引入一个第三方库对于长远来说也很有必要。
  • 虽然易用性上可能不如date4j更简单,考虑到joda-time使用更广泛资料更多这样也保证了安全和稳定性以及后续的维护代价

综上决定使用joda-time库。但是joda的parse功能并不强大,必须要结合具体的timeFormat才行,这点date4j要强大的多。

下面是根据时间字符串获取时间戳的一个例子,根据业务需求解析了几种常见的日期时间字符串格式。

private static final String dateFormat = "yyyy-MM-dd";
private static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
private static final DateTimeFormatter dateJFormat = DateTimeFormat.forPattern(dateFormat);
private static final DateTimeFormatter dateTimeJFormat = DateTimeFormat.forPattern(dateTimeFormat);
private static final DateTimeFormatter dbJFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.S");
private static final DateTimeFormatter dateTimeMillJFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
 
public static long getTimestampFromDateString(String time){
	if(StringUtils.isEmpty(time)){
		return 0L;
	}
	int len = time.length();
	try {
		DateTime dateTime = null;
		if(len == 10){
			dateTime = DateTime.parse(time, dateJFormat);
		}
		else if(len == 19){
			dateTime = DateTime.parse(time, dateTimeJFormat);
		}
		else if(len == 21){
			dateTime = DateTime.parse(time, dbJFormat);
		}
		else if(len == 23){
			dateTime = DateTime.parse(time, dateTimeMillJFormat);
		}
		if(dateTime != null){
			return dateTime.getMillis();
		}
		else{
			m_logger.error("getTimestampFromDateString joda miss : " + time);
			return new SimpleDateFormat(dateTimeFormat).parse(time).getTime();
		}
	} catch (Exception e) {
		m_logger.error("getTimestampFromDateString error : " + time, e);
		return 0L;
	}
}
Categories: Java 标签:
  1. 还没有评论呢。