java多线程环境日期类使用

154 阅读2分钟

本文已参与「新人创作礼」活动.一起开启掘金创作之路。

场景:早高峰和下午15:00挂号,存在预约日期错乱,生产上频繁出现实际挂号日期和写入数据库日期不对等故障

分析:查看问题日志,发现出问题的数据都是在早上8:00-9:00   下午15:00左右,系统该段时间为放号时间,且除了预约日期,其他字段数据写入数据库都正确,猜测是跟多线程处理日期有关。

排查方式:经过单步程序调试发现不了问题,每次结果都正确。因此单独写了个定时器模拟多线程,1分钟启动一次程序处理日期,在关键地方打出日志并错误报警,SimpleDateFormat类处理后得到日期是1970-01-00,格林乔治系统初始时间。

来看 SimpleDateFormat 类的源码注释:

image.png

说的很清楚,SimpleDateFormat 不是线程安全的,多线程下需要为每个线程创建不同的实例。不安全的原因是因为使用了 Calendar 这个全局变量:

image.png

在日期格式化的时候:

这个 time 就会出现多线程并发设置安全问题,比如 A 线程在执行设置的时候,刚好被 B 线程抢先设置了,这样时间不就错乱了。

image.png

解决方法:

1)尽量使用局部变量;

2)如果要使用全局变量,则需要加锁格式化操作;

3)使用 ThreadLocal 进行线程隔离;

正确代码如下:

DateUtils.java 工具类

 
* 锁对象
*/
private static final Object lockObj = new Object();
/

* 存放不同的日期模板格式的sdf的Map
*     private static Map<String, ThreadLocal> sdfMap = new HashMap<String, ThreadLocal>();

 **
* 多线程环境使用      *

         *
  • 方法描述:String类型转date类型
  •      * @param dateStr 日期字符串如:2020-06-06
    * @param dateFormat 日期格式,如:yyyy-MM-dd
    * @return java.util.Date
    */

        public static Date parse(String dateStr, String dateFormat) {

            try {

                return getDateFormatNew(dateFormat).parse(dateStr);

            } catch (Exception e) {

                LOG.error("parse转换错误"+ dateStr);         }

            return null;     }               /** 多线程环境使用
    *


      *
    • 方法描述:date类型转String类型
    • \

           * @param date 日期\

           * @param dateFormat 日期格式,如:yyyy-MM-dd\

           * @return java.lang.String 转换后的String日期\

           */

          public static String format(Date date, String dateFormat) {

               try {

                   return getDateFormatNew(dateFormat).format(date);

               } catch (Exception e) {

                    LOG.error("format转换错误");          }          return null;             }                 private static SimpleDateFormat getDateFormatNew(final String pattern) {

              ThreadLocal tl = sdfMap.get(pattern);

              // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf

              if (tl == null) {

                  synchronized (lockObj) {

                      tl = sdfMap.get(pattern);

                      if (tl == null) {

                          // 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map

                          // 这里是关键,使用ThreadLocal替代原来直接new SimpleDateFormat                     tl = new ThreadLocal() {                         @Override\

                              protected SimpleDateFormat initialValue() {

                                  return new SimpleDateFormat(pattern);
      }
      };
      sdfMap.put(pattern, tl);
      }
      }
      }

              return tl.get();
      }