SimpleDateFormat类的parse和format方法的线程安全问题

SimpleDateFormat类的parse和format方法的线程安全问题

一. 现象

  • 使用SimpleDateFormat#parse方法,可以将满足格式要求的字符串转换成Date对象

    public static void main(String[] args){
      String dataTime = "20221025210000";
      SimpleDateFormat YMDHMS = new SimpleDateFormat("yyyyMMddHHmmss");
      Date date = YMDHMS.parse(dataTime);
    } 
    
  • 使用SimpleDateFormat#format方法,可以将Date类型的对象转换成一定格式的字符串

    public static void main(String[] args){
    Date date = new Date();
    SimpleDateFormat YMDHMS = new SimpleDateFormat("yyyyMMddHHmmss");
    String dataTime = YMDHMS.format(date)
    }
    

但是在并发情况下会报java.lang.NumberFormatException 异常

二. 解决方法

问题的根源就是:SimpleDateFormat 被多个线程共享,它的parseformat方法就像是 i++ 一样,它维持的 Calender 等属性无法保证原子性。

  1. 使用局部变量,保证每个线程中都有一份SimpleDateFormat实例,那么,线程之间的 SimpleDateFormat实例 就没有任何关系了,不会互相影响了。
    不过,也加重了创建对象的负担,会频繁地创建和销毁对象,效率较低。

  2. 使用 ThreadLocal实现 每个线程都可以得到单独的一个SimpleDateFormat的实例,那么自然也就不存在竞争问题了

    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    
    public static Date parse(String dateStr) throws ParseException {
        return threadLocal.get().parse(dateStr);
    }
    
    public static String format(Date date) {
        return threadLocal.get().format(date);
    }
    
    
  3. 同步代码块 synchronized

    /**
     * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
     *
     * @param pattern
     * @return
     */
    public static SimpleDateFormat getSdf(final String pattern) {
        ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);
    
        // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
        if (tl == null) {
            synchronized (lockObj) {
                tl = sdfMap.get(pattern);
                if (tl == null) {
                    // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
                    tl = new ThreadLocal<SimpleDateFormat>() {
    
                        @Override
                        protected SimpleDateFormat initialValue() {
                            return new SimpleDateFormat(pattern);
                        }
                    };
                    sdfMap.put(pattern, tl);
                }
            }
        }
    
        return tl.get();
    }
       
    
    
  4. Lock锁方式,与synchronized锁方式实现原理相同,都是在高并发下通过JVM的锁机制来保证程序的线程安全。具体使用的是 Lock 的子类 ReentrantLock (可重入锁)

  5. 基于JDK1.8的 DateTimeFormatter

    // 指定格式 静态方法 ofPattern()
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    // DateTimeFormatter 自带的格式方法
    LocalDateTime now = LocalDateTime.now();
    // DateTimeFormatter 把日期对象,格式化成字符串
    String strDate1 = formatter.format(now);
    

三. 线程安全的类LocalDateTime使用

  1. LocalDateTime 和Date转换

    //将java8 的 java.time.LocalDateTime 转换为 java.util.Date,默认时区为东8区
    public  Date localDateTimeConvertToDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.toInstant(ZoneOffset.of("+8")));
    }
    
  2. 获取指定时间的前一天

    DateTimeFormatter YMDHMS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    
    public Date getPreDateForYMDHMS(String dataTime) throws ParseException {
          // String dataTime = "20221025210000"
          LocalDateTime localDateTime=LocalDateTime.parse(dataTime,YMDHMS);
          Date date = localDateTimeConvertToDate(localDateTime);
          Calendar c = Calendar.getInstance();
          c.setTime(date);
          c.add(Calendar.DAY_OF_MONTH, -1);
          return c.getTime();
    }
    
  3. 获取指定时间的时间戳(毫秒)

    DateTimeFormatter YMDHMS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    
    public  long getDateTime(String dataTime) throws ParseException {
            LocalDateTime localDateTime=LocalDateTime.parse(dataTime,YMDHMS);
            long time = localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
            return time;
    }
    
  4. 获取当前日期

    public String formatDateYmd(){
            LocalDateTime now = LocalDateTime.now();
            String strDate = YMD.format(now);
            return strDate;
        }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值