在java8以前,要格式化日期時間,就需要用到SimpleDateFormat 。
 
但我們知道SimpleDateFormat是線程不安全的,處理時要特別小心,要加鎖或者不能定義為static,要在方法內(nèi)new出對象,再進行格式化。很麻煩,而且重復(fù)地new出對象,也加大了內(nèi)存開銷。
 
后來Apache 在commons-lang 包中擴展了FastDateFormat對象,它是一個線程安全的,可以用來完美替換SimpleDateFormat。
 
來看看SimpleDateFormat的源碼
 
// 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) ; 
. . . 
    } 
問題就出在成員變量calendar ,如果在使用SimpleDateFormat時,用static定義,那SimpleDateFormat變成了共享變量。那SimpleDateFormat中的calendar 就可以被多個線程訪問到。
 
public  class  SimpleDateFormatDemoTest  { 
private  static  SimpleDateFormat  simpleDateFormat =  new  SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss" ) ; 
    public  static  void  main ( String [ ]  args)  { 
    //1、創(chuàng)建線程池 
        ExecutorService  pool =  Executors . newFixedThreadPool ( 5 ) ; 
        //2、為線程池分配任務(wù) 
        ThreadPoolTest  threadPoolTest =  new  ThreadPoolTest ( ) ; 
        for  ( int  i =  0 ;  i <  10 ;  i++ )  { 
            pool. submit ( threadPoolTest) ; 
        } 
        //3、關(guān)閉線程池 
        pool. shutdown ( ) ; 
    } 
    static  class   ThreadPoolTest  implements  Runnable { 
        private  volatile  int  i= 0 ; 
        @Override 
        public  void  run ( )  { 
            while  ( i< 10 ) { 
String  dateString =  simpleDateFormat. format ( new  Date ( ) ) ; 
try  { 
Date  parseDate =  simpleDateFormat. parse ( dateString) ; 
String  dateString2 =  simpleDateFormat. format ( parseDate) ; 
System . out. println ( Thread . currentThread ( ) . getName ( ) + " : " + i++ ) ; 
System . out. println ( dateString. equals ( dateString2) ) ; 
System . out. println ( "-------------------------" ) ; 
}  catch  ( ParseException  e)  { 
e. printStackTrace ( ) ; 
} 
            } 
        } 
    } 
} 
 
出現(xiàn)了兩次false,說明線程是不安全的。
 
 Apache Commons Lang 3.5
//FastDateFormat 
@Override 
public  String  format ( final  Date  date)  { 
   return  printer. format ( date) ; 
} 
@Override 
public  String  format ( final  Date  date)  { 
final  Calendar  c =  Calendar . getInstance ( timeZone,  locale) ; 
c. setTime ( date) ; 
return  applyRulesToString ( c) ; 
} 
源碼中 Calender 是在 format 方法里創(chuàng)建的,肯定不會出現(xiàn) setTime 的線程安全問題。這樣線程安全疑惑解決了。那還有性能問題要考慮?
 
我們來看下FastDateFormat是怎么獲取的
 
FastDateFormat . getInstance ( ) ; 
FastDateFormat . getInstance ( CHINESE_DATE_TIME_PATTERN) ; 
看下對應(yīng)的源碼
 
/**
 * 獲得 FastDateFormat實例,使用默認格式和地區(qū)
 *
 * @return FastDateFormat
 */ 
public  static  FastDateFormat  getInstance ( )  { 
   return  CACHE. getInstance ( ) ; 
} 
/**
 * 獲得 FastDateFormat 實例,使用默認地區(qū)<br>
 * 支持緩存
 *
 * @param pattern 使用{@link java.text.SimpleDateFormat} 相同的日期格式
 * @return FastDateFormat
 * @throws IllegalArgumentException 日期格式問題
 */ 
public  static  FastDateFormat  getInstance ( final  String  pattern)  { 
   return  CACHE. getInstance ( pattern,  null ,  null ) ; 
} 
這里有用到一個CACHE,看來用了緩存,往下看
 
private  static  final  FormatCache < FastDateFormat > =  new  FormatCache < FastDateFormat > ( ) { 
   @Override 
   protected  FastDateFormat  createInstance ( final  String  pattern,  final  TimeZone  timeZone,  final  Locale  locale)  { 
      return  new  FastDateFormat ( pattern,  timeZone,  locale) ; 
   } 
} ; 
// 
abstract  class  FormatCache < F  extends  Format > { 
    . . . 
    private  final  ConcurrentMap < Tuple ,  F > =  new  ConcurrentHashMap < > ( 7 ) ; 
private  static  final  ConcurrentMap < Tuple ,  String > =  new  ConcurrentHashMap < > ( 7 ) ; 
    . . . 
} 
 
在getInstance 方法中加了ConcurrentMap 做緩存,提高了性能。且我們知道ConcurrentMap 也是線程安全的。
 
/**
 * 年月格式 {@link FastDateFormat}:yyyy-MM
 */ 
public  static  final  FastDateFormat  NORM_MONTH_FORMAT =  FastDateFormat . getInstance ( NORM_MONTH_PATTERN) ; 
 
//FastDateFormatpublic static FastDateFormat getInstance(final String pattern) {   return CACHE.getInstance(pattern, null, null);} 
 
 
如圖可證,是使用了ConcurrentMap 做緩存。且key值是格式,時區(qū)和locale(語境)三者都相同為相同的key。
 
java8之前,可使用FastDateFormat 替換SimpleDateFormat,達到線程安全的目的;
 
java8及以上的,java8提供了一套新的日期時間API,可以使用DateTimeFormatter來代替SimpleDateFormat。具體的源碼分析,可以看這里,傳送門:萬字博文教你搞懂java源碼的日期和時間相關(guān)用法