安全优雅的使用SimpleDateFormat类处理时间

Posted yliucnblogs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安全优雅的使用SimpleDateFormat类处理时间相关的知识,希望对你有一定的参考价值。

@


缘起:前天公司出个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;
    }

}







以上是关于安全优雅的使用SimpleDateFormat类处理时间的主要内容,如果未能解决你的问题,请参考以下文章

(转)关于SimpleDateFormat安全的时间格式化线程安全问题

关于SimpleDateFormat线程安全问题

关于SimpleDateFormat线程安全问题

SimpleDateFormat类的线程安全问题和解决方案

SimpleDateFormat时间格式化存在线程安全问题

案例-- 线程不安全对象(SimpleDateFormat)