日期時間格式化 SimpleDateFormat與DateTimeFormatter


原文:https://www.jianshu.com/p/b212afa16f1f

 

1.SimpleDateFormat為什么不是線程安全的?

  • 如果我們把SimpleDateFormat定義成static成員變量,那么多個thread之間會共享這個SimpleDateFormat對象, 所以Calendar對象也會共享。
public static SimpleDateFormat formater = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); System.out.println(formater.format(new Date())+" Exception made..."); 
  • DateFormat.java
    public final String format(Date date) { return format(date, new StringBuffer(), DontCareFieldPosition.INSTANCE).toString(); } public abstract StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition); 
  • SimpleDateFormat.java
    @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) { // 如此輕易地使用內部變量,肯定不能線程安全 // 線程都對pos進行寫操作,必然會影響其他線程的讀操作 pos.beginIndex = pos.endIndex = 0; return format(date, toAppendTo, pos.getFieldDelegate()); } 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; } 

1.1 復現錯誤

代碼參考

public class DateFormatTest { private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); private static String date[] = { "01-Jan-1999", "09-Jan-2000", "08-Jan-2001" , "07-Jan-2002" , "06-Jan-2003" , "05-Jan-2004" , "04-Jan-2005" , "03-Jan-2006" , "02-Jan-2007" }; public static void main(String[] args) { for (int i = 0; i < date.length; i++) { final int temp = i; new Thread(new Runnable() { @Override public void run() { try { while (true) { String str1 = date[temp]; String str2 = sdf.format(sdf.parse(str1)); System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2); if(!str1.equals(str2)){ throw new RuntimeException(Thread.currentThread().getName() + ", Expected " + str1 + " but got " + str2); } } } catch (Exception e) { throw new RuntimeException("parse failed", e); } } }).start(); } } } 
 
 

2.SimpleDateFormat線程不安全的解決方法

2.1 將SimpleDateFormat定義成局部變量:

SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); String str1 = "01-Jan-2010"; String str2 = sdf.format(sdf.parse(str1)); 

缺點:每調用一次方法就會創建一個SimpleDateFormat對象,方法結束又要作為垃圾回收。

2.2 加一把線程同步鎖:synchronized(lock)

public class SyncDateFormatTest { private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" }; public static void main(String[] args) { for (int i = 0; i < date.length; i++) { final int temp = i; new Thread(new Runnable() { @Override public void run() { try { while (true) { synchronized (sdf) { String str1 = date[temp]; Date date = sdf.parse(str1); String str2 = sdf.format(date); System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2); if(!str1.equals(str2)){ throw new RuntimeException(Thread.currentThread().getName() + ", Expected " + str1 + " but got " + str2); } } } } catch (Exception e) { throw new RuntimeException("parse failed", e); } } }).start(); } } } 

缺點:性能較差,每次都要等待鎖釋放后其他線程才能進入

2.3 使用ThreadLocal

每個線程都將擁有自己的SimpleDateFormat對象副本。

public class DateUtil { private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>(); public static Date parse(String str) throws Exception { SimpleDateFormat sdf = local.get(); if (sdf == null) { sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); local.set(sdf); } return sdf.parse(str); } public static String format(Date date) throws Exception { SimpleDateFormat sdf = local.get(); if (sdf == null) { sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); local.set(sdf); } return sdf.format(date); } } 
public class ThreadLocalDateFormatTest { private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" }; public static void main(String[] args) { for (int i = 0; i < date.length; i++) { final int temp = i; new Thread(new Runnable() { @Override public void run() { try { while (true) { String str1 = date[temp]; Date date = DateUtil.parse(str1); String str2 = DateUtil.format(date); System.out.println(str1 + "," + str2); if(!str1.equals(str2)){ throw new RuntimeException(Thread.currentThread().getName() + ", Expected " + str1 + " but got " + str2); } } } catch (Exception e) { throw new RuntimeException("parse failed", e); } } }).start(); } } } 

3.使用DateTimeFormatter代替SimpleDateFormat

代碼參考DateTimeFormatterTest.java
jdk1.8中新增了 LocalDate 與 LocalDateTime等類來解決日期處理方法,同時引入了一個新的類DateTimeFormatter來解決日期格式化問題。
LocalDateTime,DateTimeFormatter兩個類都沒有線程問題,只要你自己不把它們創建為共享變量就沒有線程問題。
可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd"); LocalDate date = LocalDate.parse("2017 06 17", formatter); System.out.println(formatter.format(date)); 

 自測類

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class DateFormatTest {
    private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy", Locale.US);
    private static String date[] = { "01-01-1999", "09-01-2000", "08-01-2001" , "07-01-2002" , "06-01-2003" , "05-01-2004" , "04-01-2005" , "03-01-2006" , "02-01-2007" };
    
    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
    

    public static void main(String[] args) {
        for (int i = 0; i < date.length; i++) {
            final int temp = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            String str1 = date[temp];
                            
                            //線程安全
                            LocalDate date = LocalDate.parse(str1, formatter);
                            String str2 =  formatter.format(date);
                            
                            //線程不安全
//                            String str2 = sdf.format(sdf.parse(str1));
                            
                            System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
                            if(!str1.equals(str2)){
                                throw new RuntimeException(Thread.currentThread().getName()
                                        + ", Expected " + str1 + " but got " + str2);
                            }
                        }
                    } catch (Exception e) {
                        throw new RuntimeException("parse failed", e);
                    }
                }
            }).start();
        }
    }
}

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM