SimpleDateFormat 线程不安全问题及解决方案

零、概述

任何线程不安全的问题,其实本质就是共用了一份数据且没有进行加锁同步,SimpleDateFormat 也是一样。

一、错误案例

public class Test {

    static DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws Exception{
        
        ExecutorService ts = Executors.newFixedThreadPool(1000);
        for (;;) {
            ts.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        String format =  df.format(new Date(Math.abs(new Random().nextLong())));
                        System.out.println(format);
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.exit(1);
                    }
                }
            });
        }


    }


}

运行后出现报错:以下只是报错的一种,多线程场景下会出现多种不同的错误

192456611-09-28 19:38:41
26312543-11-28 13:35:35
233029168-10-09 00:28:24
100149768-11-04 22:59:40
141560024-07-30 14:30:46
52461160-03-13 16:40:29
131434392-08-03 13:32:17
246393230-02-21 08:50:04
71490728-12-01 23:32:16
284007186-04-28 14:34:21
184430351-11-02 17:30:38
49670911-09-27 20:43:33java.lang.ArrayIndexOutOfBoundsException: -2850986
	at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:453)
	at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2397)
	at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2312)
	at java.util.Calendar.complete(Calendar.java:2268)
	at java.util.Calendar.get(Calendar.java:1826)
	at java.text.SimpleDateFormat.subFormat(SimpleDateFormat.java:1119)
	at java.text.SimpleDateFormat.format(SimpleDateFormat.java:966)
	at java.text.SimpleDateFormat.format(SimpleDateFormat.java:936)
	at java.text.DateFormat.format(DateFormat.java:345)
	at test.Test$1.run(Test.java:102)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

SimpleDateFormat 注释里面就描述了,此类为非线程安全的,所以上述用了 static 或者定义为全局变量在多线程场景就会出现问题。

二、原理分析

SimpleDateFormat.java



private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

        boolean useDateFormatSymbols = useDateFormatSymbols();

        ......
}

可以看到传入的 Date 是赋值在全局变量 calendar 上的,那么在多线程场景,calendar 就会被覆盖产生数据错乱,数组越界等问题。

三、解决方案

public class Test {

   
    static DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws Exception{
        
        ExecutorService ts = Executors.newFixedThreadPool(100);
        for (;;) {
            ts.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Instant instant = new Date(Math.abs(new Random().nextLong())).toInstant();
                        ZoneId zone = ZoneId.systemDefault();
                        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);

                        System.out.println(df.format(localDateTime));
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.exit(1);
                    }
                }
            });
        }



    }


}

上述使用 JDK1.8 的方案,实现类是线程安全的,就解决了问题。

当然还有其他方案,比如

1、将SimpleDateFormat定义成局部变量

      缺点:定义为局部对象,每调用一次方法就会创建一个 SimpleDateFormat 对象,增加GC对象

2、方法加同步锁synchronized,在同一时刻,只有一个线程可以执行类中的某个方法

     缺点:性能较差

发布了159 篇原创文章 · 获赞 350 · 访问量 55万+

猜你喜欢

转载自blog.csdn.net/wenniuwuren/article/details/103888880