美文网首页
02-SimpleDateFormat为什么线程不安全

02-SimpleDateFormat为什么线程不安全

作者: 蜗牛写java | 来源:发表于2020-10-10 23:54 被阅读0次

    02-SimpleDateFormat为什么线程不安全

    背景

    阿里巴巴java开发手册中有这么一条:

    【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。

    那么今天我们来分析下SimpleDateFormat为什么是线程不安全的;

    其实jdk8不再推荐这样使用了,可以使用LocalDate(不可以变类);以下只是感兴趣,探个究竟

    错误的例子

    先看下错误的例子:

    /**
     * @Description SimpleDateFormat 为什么线程不安全
     * @Date 2020/10/10 9:26 PM
     * @Created by dwb
     * 微信: snail_java
     */
    public class DateUtils {
    
        private DateUtils() {}
    
        private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        public static String formatDate(Date date) {
            return DATE_FORMAT.format(date);
        }
    
        public static Date parseDateStr(String dateStr) {
            try {
                //为类变异代码阅读,异常在工具方法中try了
                return DATE_FORMAT.parse(dateStr);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static void main(String[] args) {
            final String dateStr = "2020-10-10 10:10:10";
    
            //按照Java规范;线程应该放入线程池中执行;此处为了阅读方便,线程单独执行;
            for (int i = 0; i < 10; i++) {
                Runnable runnable = () -> DateUtils.parseDateStr(dateStr);
                new Thread(runnable).start();
            }
    
        }
    }
    

    运行后

    Exception in thread "Thread-5" Exception in thread "Thread-2" Exception in thread "Thread-3" Exception in thread "Thread-1" Exception in thread "Thread-0" Exception in thread "Thread-6" java.lang.NumberFormatException: empty String
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at com.dwb.snail.day.day02.DateUtils.parseDateStr(DateUtils.java:28)
    at com.dwb.snail.day.day02.DateUtils.lambdamain0(DateUtils.java:40)
    at java.lang.Thread.run(Thread.java:748)
    java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at com.dwb.snail.day.day02.DateUtils.parseDateStr(DateUtils.java:28)
    at com.dwb.snail.day.day02.DateUtils.lambdamain0(DateUtils.java:40)
    at java.lang.Thread.run(Thread.java:748)

    看下源码结构

    SimpleDateFormat源码.png

    可以看出 SimpleDateFormat 继承 DateFormat;然而DateFormat有两个protected属性(calendar,numberFromat);所以SimpleDateFormat也继承了这两个属性;

    因此,多线程情况下,SimpleDateFormat具有两个共享的变量,即calendar(主要存放日期),numberFromat多线程情况下修改共享变量,是线程不安全的

    • parse过程线程不安全分析

      共享变量calendar

      parse中有CalendarBuilder;该builder构建calendar;多线程构建,相当于多个线程同时给属性 set值,所以不安全

    • format过程线程不安全分析

      共享变了calendar

      实际上给calendar设置date,多线程同时set date是不安全的,再调用subFormat将date转换为字符串

    如何正确使用SimpleDateFormat

    1. 每次调用时候,创建SimpleDateFormat(不推荐)

      /**
       * @Description 线程安全SimpleDateFormat
       * @Date 2020/10/10 11:42 PM
       * @Created by dwb
       * 微信: snail_java
       */
      public class OK1DateUtils {
      
          private OK1DateUtils() {
          }
      
          public static String formatDate(Date date) {
              SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
              return format.format(date);
          }
      
          public static Date parseDateStr(String dateStr) {
              try {
                  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                  //为类变异代码阅读,异常在工具方法中try了
                  return format.parse(dateStr);
              } catch (ParseException e) {
                  e.printStackTrace();
              }
              return null;
          }
      }
      
    1. 使用ThreadLocal (推荐使用)
    /**
     * @Description SimpleDateFormat线程安全使用方式
     * @Date 2020/10/10 11:46 PM
     * @Created by dwb
     * 微信: snail_java
     */
    public class OK2DateUtils {
    
        private static final ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
            @Override
            protected DateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        };
    
        public static String formatDate(Date date) {
            return threadLocal.get().format(date);
        }
    
        public static Date parseDateStr(String dateStr) {
            try {
                //为类变异代码阅读,异常在工具方法中try了
                return threadLocal.get().parse(dateStr);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static void main(String[] args) {
            final String dateStr = "2020-10-10 10:10:10";
    
            //按照Java规范;线程应该放入线程池中执行;此处为了阅读方便,线程单独执行;
            for (int i = 0; i < 10; i++) {
                Runnable runnable = () -> OK2DateUtils.parseDateStr(dateStr);
                new Thread(runnable).start();
            }
    
        }
    }
    

    相关文章

      网友评论

          本文标题:02-SimpleDateFormat为什么线程不安全

          本文链接:https://www.haomeiwen.com/subject/taespktx.html