@
缘起:前天公司出个bug,经排查SimpleDateFormat时间处理类使用的单例。。。
SimpleDateFormat为什么强制建议不能static修饰
SimpleDateFormat类内部有一个Calendar对象引用,它用来储存和这个类相关的日期信息,例如parse()方法,format()方法,诸如此类的方法参数传入的日期相关String,Date等等,都是Calendar引用来储存的,这样就会导致一个问题,如果SimpleDateFormat是static修饰的,那么多个线程之间就会共享这个类,同时也是共享这个Calendar引用。
parse()方法
1、使用的共享变量calendar(父类DateFormat中的属性),而这个共享变量的访问没有做到线程安全;
2、parse()方法中创建了CalendarBuilder类,然后通过CalendarBuilder#establish()方法操作calendar,最后返回calendar。
format()方法
1、使用的共享变量calendar(父类DateFormat中的属性),而这个共享变量的访问没有做到线程安全;
2、使用format()方法实际使用calendar共享变量设置date值,然后调用subFormat()将date转化成字符串。
解决方案
1、使用线程局部变量;
2、每次使用直接创建(new SimpleDateFormat())
高可用工具类推荐
/**
* 日期工具类
* @see java.util.Map
* @see java.text.DateFormat
* @see java.lang.ThreadLocal
* @see java.text.SimpleDateFormat
* @author yang.Liu
*/
public class DateUtils {
private static final Logger log = LoggerFactory.getLogger(DateUtils.class);
private DateUtils() {}
/**
* Date转为字符串日期
* @param date 日期
* @param dateFormat 日期格式
*/
public static String format(Date date, DateFormatEnum dateFormat) {
return getDateFormat(dateFormat).format(date);
}
/**
* Date转为默认字符串日期
* @param date 日期
*/
public static String format(Date date) {
return format(date, DateFormatEnum.DEFAULT_FORMAT);
}
/**
* 字符串日期转为Date
* @param strDate 字符串日期
*/
public static Date parse(String strDate) {
return parse(strDate, DateFormatEnum.DEFAULT_FORMAT);
}
/**
* 字符串日期转为Date
* @param strDate 字符串日期
* @param dateFormat 日期格式
*/
public static Date parse(String strDate, DateFormatEnum dateFormat) {
try {
return getDateFormat(dateFormat).parse(strDate);
} catch (ParseException e) {
log.error("字符串日期转为Date异常", e);
return null;
}
}
/**
* Thread线程变量 + Map | 缓存 日期格式(K),SimpleDateFormat(V),提高效率
* @param dateFormat {@link DateFormatEnum}
* {@link #TL()}
*/
private static DateFormat getDateFormat(DateFormatEnum dateFormat) {
ThreadLocal<Map<DateFormatEnum, DateFormat>> TL = TL();
Map<DateFormatEnum, DateFormat> map = TL.get();
if (Objects.isNull(map)) {
map = new HashMap<>();
TL.set(map);
}
if (Objects.isNull(dateFormat)) {
dateFormat = DateFormatEnum.DEFAULT_FORMAT;
}
DateFormat ret = map.get(dateFormat);
if (Objects.isNull(ret)) {
ret = new SimpleDateFormat(dateFormat.dateFormat);
map.put(dateFormat, ret);
}
return ret;
}
/**
* 时间格式化枚举,由于可读性,枚举对象声明未遵循常量大写规范~
*/
public enum DateFormatEnum {
DEFAULT_FORMAT("yyyy-MM-dd HH:mm:ss"),
yyyy_MM_dd("yyyy-MM-dd"),
yyyy("yyyy"),
MM("MM"),
dd("dd"),
HH_mm_ss("HH:mm:ss"),
HH("HH"),
mm("mm"),
ss("ss"),
SSS("SSS"),
yyyyMMddHHmmss("yyyyMMddHHmmss"),
yyyy_MM_dd__HH_mm_ss__SSS("yyyy-MM-dd HH:mm:ss SSS"),
yyyyMMddHHmmssSSS("yyyyMMddHHmmssSSS"),
;
private final String dateFormat;
DateFormatEnum(String dateFormat) {
this.dateFormat = dateFormat;
}
}
/**
* 静态内部类声明单例Thread线程变量
*/
private static class SingletonHolder {
private static final ThreadLocal<Map<DateFormatEnum, DateFormat>> TL = new ThreadLocal<>();
}
/**
* 初始化调用
*/
private static ThreadLocal<Map<DateFormatEnum, DateFormat>> TL() {
return SingletonHolder.TL;
}
}