SimpleDateFormat线程安全问题和解决方案

Posted 泡^泡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SimpleDateFormat线程安全问题和解决方案相关的知识,希望对你有一定的参考价值。

为什么线程不安全

在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。

public StringBuffer format(Date date, StringBuffer toAppendTo,FieldPosition pos)
        pos.beginIndex = pos.endIndex = 0;
        return format(date,toAppendTo,pos.getFieldDelegate());
    

    // Called from Format after creating a FieldDelegate

    private StringBuffer format(Date date,StringBuffer toAppendTo,FieldDelegate delegate) 
        // Convert input date to time field list
        calendar.setTime(date);
        boolean useDateFormatSymbols = useDateFormatSymbols();
        for (int i = 0; i < compiledPattern.length;) 
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) 
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            
            switch (tag) 
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;
            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;
            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
            
        
        return toAppendTo;
    
protected Calendar calendar;

可以看到,多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。

此外,parse方法也是线程不安全的,parse方法实际调用的是CalenderBuilder的establish来进行解析,其方法中主要步骤不是原子操作。

解决方案

  • 使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本
  • 加一把线程同步锁:synchronized(lock)
  • 将SimpleDateFormat定义成局部变量
  • 使用DateTimeFormatter代替SimpleDateFormat
    ,DateTimeFormatter是线程安全的,默认提供了很多格式化方法,也可以通过ofPattern方法创建自定义格式化方法。
package com;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleDateFormatTest 

    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal();

    private static DateFormat getDateFormat()
        //从当前线程的范围内获得一个DateFormat
        DateFormat dateFormat = threadLocal.get();
        if(dateFormat == null)
            dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //Thread.currentThread范围内设置一个SimpleDateFormat
            threadLocal.set(dateFormat);
        
        return dateFormat;
    

    public static Date parse(String strDate) throws ParseException 
       return getDateFormat().parse(strDate);
    

    public static void main(String[] args) 
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 20;i++)
            executorService.execute(()->
                try 
                    System.out.println(parse("2022-07-09 10:00:00"));
                 catch (ParseException e) 
                    e.printStackTrace();
                
            );
        
    

格式化日期示例:

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // 2019-11-20T15:04:29.017
DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String strDate=localDateTime.format(dtf);
System.out.println(strDate); // 2019/23/20 15:23:46

解析日期:

DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime localDateTime=LocalDateTime.parse("2019/11/20 15:23:46",dtf);
System.out.println(localDateTime); // 2019-11-20T15:23:46

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

SimpleDateFormat线程不安全及解决办法(转)

SimpleDateFormat线程不安全的5种解决方案!

SimpleDateFormat线程不安全了?这里有5种解决方案

SimpleDateFormat线程安全问题解决方案

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

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