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

Posted wenniuwuren

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,在同一时刻,只有一个线程可以执行类中的某个方法

     缺点:性能较差

 

 

 

 

 

 

以上是关于SimpleDateFormat 线程不安全问题及解决方案的主要内容,如果未能解决你的问题,请参考以下文章

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

SimpleDateFormat线程不安全原因及解决方案

SimpleDateFormat线程不安全及解决的方法

Java Review - SimpleDateFormat线程不安全原因的源码分析及解决办法

Java Review - SimpleDateFormat线程不安全原因的源码分析及解决办法

并发条件下SimpleDateFormat线程不安全及解决方案