SimpleDateFormat多线程下的安全问题

首先们知道SimpleDateFormat是线程不安全的,那么让SimpleDateFormat变得线程安全呢,接下来的三个实验一步一步教会你。

首先我们来看一下SimpleDateFormat是如何线程不安全的.

首先创建一个工具类把SimpleDateFormat的两个方法包成类中的方法供调用

public class DateUtil {
   

    private static final String PATTERN = "yyyy-MM-dd HH-mm-ss";
    private static  DateFormat dateFormat = new SimpleDateFormat(PATTERN);
     //SimpleDateFormat是线程不安全,不能作为成员变量使用
    //Str --Date
    public static Date strConvertToDate(String dateStr) {
   
        Objects.requireNonNull(dateStr);

        Date parse = null;
        try {
   
            parse = dateFormat.parse(dateStr);
        } catch (ParseException e) {
   
            e.printStackTrace();
        }
        return parse;
    }

    //Date转换str
    public static String DateConvertToStr(Date date) {
   
        Objects.requireNonNull(date);
        String format = dateFormat.format(date);
        return format;
     }
}

创建一个类调用方法实现线程不安全.这个类中实现了两个进程一起进行

这就让两个进程都同时使用了SimpleDateFormat创建的dateFormat对象

public class DateTest {
   
    public static void main(String[] args) {
   
        String[] dateStr = {
   
                "2020-12-12 12-12-12",
                "2020-12-12 12-12-12",
                "2020-12-12 12-12-12",
                "2020-12-12 12-12-12",
                "2020-12-12 12-12-12",
                "2020-12-12 12-12-12"
        };


        //实在多线程的环境下会出现问题
        new Thread(() -> {
   
            for (String s : dateStr) {
   
                Date date = DateUtil.strConvertToDate(s);
                System.out.println(Thread.currentThread().getName() + date);
            }
        }).start();


        new Thread(()->{
   
            for (String s : dateStr) {
   
                Date date = DateUtil.strConvertToDate(s);
                System.out.println(Thread.currentThread().getName()+date);
            }
        }).start();
    }
}

结果如下:此时报错

Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: -1
Thread-0Sat Dec 12 12:12:12 CST 2020
	at java.text.DigitList.fitsIntoLong(DigitList.java:230)
Thread-0Sat Dec 12 12:12:12 CST 2020
Thread-0Sat Dec 12 12:12:12 CST 2020
Thread-0Sat Dec 12 12:12:12 CST 2020
	at java.text.DecimalFormat.parse(DecimalFormat.java:2082)
	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 exercise.DateUtil.strConvertToDate(DateUtil.java:28)
	at exercise.DateTest.lambda$main$1(DateTest.java:36)
	at java.lang.Thread.run(Thread.java:748)
Thread-0Sat Dec 12 12:12:12 CST 2020
Thread-0Sat Dec 12 12:12:12 CST 2020

-------------------------------------------------分隔符------------------------------------------------------

如何实现线程安全?

<mark>方法一</mark>:修改DateUtil中的dateFormat对象位置作为局部变量,在每一个方法中都添加一个dateFormat对象。此时线程安全但是创建 了大量的dateFormat对象(浪费空间)。每次实现一个方法都创建了一个dateFormat对象

public class DateUtil {
   

    private static final String PATTERN = "yyyy-MM-dd HH-mm-ss";

    //SimpleDateFormat是线程不安全,不能作为成员变量使用
    //Str --Date
    public static Date strConvertToDate(String dateStr) {
   
        Objects.requireNonNull(dateStr);

        Date parse = null;
        try {
   
            DateFormat dateFormat = new SimpleDateFormat(PATTERN);
            parse = dateFormat.parse(dateStr);
        } catch (ParseException e) {
   
            e.printStackTrace();
        }
        return parse;
    }

    //Date转换str
    public static String DateConvertToStr(Date date) {
   
        Objects.requireNonNull(date);
        DateFormat dateFormat = new SimpleDateFormat(PATTERN);
        String format = dateFormat.format(date);
        return format;
     }
}

<mark>方法二</mark>:作为成员变量存在可以使用synchronized解决 但是效率极低

public class DateUtil {
   

    private static final String PATTERN = "yyyy-MM-dd HH-mm-ss";

    //SimpleDateFormat是线程不安全,不能作为成员变量使用
    //Str --Date
    public static synchronized Date strConvertToDate(String dateStr) {
   
        Objects.requireNonNull(dateStr);
        Date parse = null;
        try {
   
            DateFormat dateFormat = new SimpleDateFormat(PATTERN);
            parse = dateFormat.parse(dateStr);
        } catch (ParseException e) {
   
            e.printStackTrace();
        }
        return parse;
    }
    //Date转换str
    public static synchronized String DateConvertToStr(Date date) {
   
        Objects.requireNonNull(date);
        DateFormat dateFormat = new SimpleDateFormat(PATTERN);
        String format = dateFormat.format(date);
        return format;
     }
}

<mark>方法三</mark>终极大法!解决内存过大和提高效率问题(ThreadLocal:以空间换时间)

每个线程各自有一个SimpleDateFormat对象 相互不干扰 代表着SimpleDateFormat的创建 删除 修改都交给ThreadLocal处理

2个线程 创建两个SimpleDateFormat对象

public class DateUtil {
   

    private static final String PATTERN = "yyyy-MM-dd HH-mm-ss";
    //SimpleDateFormat是线程不安全,不能作为成员变量使用
    //Str --Date
    //交给ThreadLocal管理
    //默认执行initialValue
    //ThreadLocal的hashcode是pattern的hahcode执行了两次方法,但是hashcode值是一样的
    private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal(){
   
        //重写initialValue
        @Override
        protected SimpleDateFormat initialValue() {
   
            return new SimpleDateFormat(PATTERN);
        }
    };

    public static synchronized Date strConvertToDate(String dateStr) {
   
        Objects.requireNonNull(dateStr);
        Date parse = null;
        try {
   
            parse = THREAD_LOCAL.get().parse(dateStr);
        } catch (ParseException e) {
   
            e.printStackTrace();
        }
        return parse;
    }
    //Date转换str
    public static synchronized String DateConvertToStr(Date date) {
   
        Objects.requireNonNull(date);
        String format = THREAD_LOCAL.get().format(date);
        return format;
     }
}