欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SimpleDateFormat非线程安全

程序员文章站 2022-03-22 14:08:56
...

 

 

问题的引出,这是一个多线程访问SimpleDateFormat的测试类

public class DateUtilTest {

	public static class TestSimpleDateFormatThreadSafe extends Thread {

		@Override
		public void run() {
			while ( true ) {
				try {
					this.join(2000);
				} catch ( InterruptedException e1 ) {
					e1.printStackTrace();
				}
				try {
					System.out.println(this.getName() + ":" + DateUtil.parse("2013-05-24 06:02:20"));
				} catch ( ParseException e ) {
					e.printStackTrace();
				}
			}
		}
	}


	public static void main( String[] args ) {
		for ( int i = 0 ; i < 10 ; i++ ) {
			new TestSimpleDateFormatThreadSafe().start();
		}

	}
}

  运行时会出现如下Exception

java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: multiple points

 原因是DateFormate类中有个Calendar变量是全局,这个变量在子类SimpleDateFormat类在两个方法中被调用

private StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate)
private void subFormat(int patternCharIndex, int count,FieldDelegate delegate, StringBuffer buffer,boolean useDateFormatSymbols)

这时多线程访问就是非线程安全的

public abstract class DateFormat extends Format {
    protected Calendar calendar;
}

 

解决办法:

方法一:每次创建都new一个新的

public class DateUtil {
    
    public static  String formatDate(Date date)throws ParseException{
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }
    
    public static Date parse(String strDate) throws ParseException{
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(strDate);
    }
}

 方法二:使用同步

public class DateSyncUtil {

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      
    public static String formatDate(Date date)throws ParseException{
        synchronized(sdf){
            return sdf.format(date);
        }  
    }
    
    public static Date parse(String strDate) throws ParseException{
        synchronized(sdf){
            return sdf.parse(strDate);
        }
    } 
}

方法三:使用ThreadLocal

public class ConcurrentDateUtil {

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

		@Override
		protected DateFormat initialValue() {
			return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		}
	};


	public static Date parse( String dateStr ) throws ParseException {
		return threadLocal.get().parse(dateStr);
	}


	public static String format( Date date ) {
		return threadLocal.get().format(date);
	}
}

 另一中ThreadLocal方法

public class ThreadLocalDateUtil {
    private static final String date_format = "yyyy-MM-dd HH:mm:ss";
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(); 
 
    public static DateFormat getDateFormat()   
    {  
        DateFormat df = threadLocal.get();  
        if(df==null){  
            df = new SimpleDateFormat(date_format);  
            threadLocal.set(df);  
        }  
        return df;  
    }  

    public static String formatDate(Date date) throws ParseException {
        return getDateFormat().format(date);
    }

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

  

 commons-lang.jar中提供了FastDateFormt,是一个thread-safe,性能上也能还是可以的

FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd", TimeZone.getDefault(), Locale.getDefault());
DateFormatUtils.ISO_DATE_FORMAT.format(new Date())

 DateFormatUtils是一个FastDateFormat工具类,提供了一些format常量

ISO_DATE_FORMAT
yyyy-MM-dd"2004-01-02"
ISO_DATE_TIME_ZONE_FORMAT
yyyy-MM-ddZZ"2004-01-02-07:00"
ISO_DATETIME_FORMAT
yyyy-MM-dd'T'HH:mm:ss"2004-01-02T23:22:12"
ISO_DATETIME_TIME_ZONE_FORMAT
yyyy-MM-dd'T'HH:mm:ssZZ"2004-01-02T21:13:45-07:00"
ISO_TIME_FORMAT
'T'HH:mm:ss"T04:23:22"
ISO_TIME_NO_T_FORMAT
HH:mm:ss"05:12:34"
ISO_TIME_NO_T_TIME_ZONE_FORMAT
HH:mm:ssZZ"12:32:22-07:00"
ISO_TIME_TIME_ZONE_FORMAT
'T'HH:mm:ssZZ"T18:23:22-07:00"
SMTP_DATETIME_FORMAT
EEE, dd MMM yyyy HH:mm:ss Z"Wed, 01 Feb 2004 20:03:01 CST"