zoukankan      html  css  js  c++  java
  • 安全优雅的使用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;
        }
    
    }
    
  • 相关阅读:
    SpringBoot系列之切换log4j日志框架
    SpringBoot系列之日志框架使用教程
    SpringBoot系列之集成logback实现日志打印(篇二)
    源码学习系列之SpringBoot自动配置(篇二)
    SpringBoot系列之@Conditional注解用法简介
    7.Maven命令
    6.Maven构建过程的各个环节
    5.Maven坐标
    4.用IntelliJ IDEA 创建Maven Web
    3.用IntelliJ IDEA 创建Maven
  • 原文地址:https://www.cnblogs.com/yliucnblogs/p/13194202.html
Copyright © 2011-2022 走看看