02-SimpleDateFormat为什么线程不安全

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.lambda0(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.lambda0(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 threadLocal = new ThreadLocal() {
        @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为什么线程不安全)