2010-11-05 98 views
81

SimpleDateFormat的javadoc指出SimpleDateFormat不同步。同步對SimpleDateFormat的訪問

「的日期格式不同步。這 建議爲每個線程創建獨立的 格式實例。如果 多個線程同時訪問一個格式 同時,它必須保持外部同步 。」

但在多線程環境中使用SimpleDateFormat實例的最佳方法是什麼?以下是我想到的一些選項,過去我使用過選項1和2,但我很想知道是否有更好的選擇,或者哪些選項可以提供最佳性能和併發性。

選項1:需要

public String formatDate(Date d) { 
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 
    return sdf.format(d); 
} 

選項2時創建本地實例:創建SimpleDateFormat的實例爲類變量而是將其同步訪問。

private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 
public String formatDate(Date d) { 
    synchronized(sdf) { 
     return sdf.format(d); 
    } 
} 

方案3:創建一個ThreadLocal存儲的SimpleDateFormat的不同實例,爲每個線程。

private ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>(); 
public String formatDate(Date d) { 
    SimpleDateFormat sdf = tl.get(); 
    if(sdf == null) { 
     sdf = new SimpleDateFormat("yyyy-MM-hh"); 
     tl.set(sdf); 
    } 
    return sdf.format(d); 
} 
+10

+1提出這個問題。許多人認爲SimpleDateFormat是線程安全的(我在各處都能看到假設)。 – 2011-03-03 18:50:39

+0

有關ThreadLocal方法的詳細信息,請參閱: http://www.javaspecialists.eu/archive/Issue172.html – miner49r 2012-02-04 14:02:11

+0

爲什麼請參閱此問題:http://stackoverflow.com/questions/6840803/simpledateformat-線程安全 – Raedwald 2013-06-13 12:17:46

回答

40
  1. 創建SimpleDateFormat是一個expensive。除非很少做,否則不要使用它。

  2. 好的,如果你可以忍受一點阻塞。如果formatDate()用處不大,則使用。

  3. 最快選項如果您重用線程(thread pool)。使用比2更多的內存,並具有更高的啓動開銷。

對於應用程序都2和3是可行的選擇。哪個最適合你的情況取決於你的用例。謹防過早優化。只有在你認爲這是一個問題時纔會這樣做。

對於庫,將通過第三方使用我會使用選項3.

+0

如果我們使用Option-2並聲明'SimpleDateFormat'作爲一個實例變量,那麼我們可以使用'synchronized block'使它成爲線程安全的。但聲納顯示警告[squid-AS2885](https://sonarqube.com/coding_rules#rule_key=squid%3AS2885)。有什麼辦法解決聲納問題嗎? – 2016-08-28 23:38:31

4

不要使用SimpleDateFormat的,使用喬達時間的DateTimeFormatter代替。它在解析方面有點嚴格,所以不是SimpleDateFormat替代品的下降,但是在安全性和性能方面,joda-time更具併發性。

23

另一種選擇是共享郎FastDateFormat,但你只能用它的日期格式,而不是解析。

與Joda不同,它可以作爲格式化的直接替代品。 (更新:由於V3.3.2,FastDateFormat可以產生FastDateParser,這是一個下拉線程安全的替代品的SimpleDateFormat)

+8

由於Commons Lang 3.2,'FastDateFormat'也有'parse()'方法 – manuna 2014-04-24 10:52:42

3

我會說,創建一個簡單的包裝類的SimpleDateFormat的是同步訪問解析( )和format(),可以用作插入式替換。比第二種選擇更簡單,比第三種選擇更簡單。

看起來像是製作SimpleDateFormat非同步是Java API設計者的一個糟糕的設計決定;我懷疑任何人都希望format()和parse()需要同步。

0

想象一下你的應用程序有一個線程。爲什麼要同步對SimpleDataFormat變量的訪問呢?

6

Commons Lang 3.x現在具有FastDateParser以及FastDateFormat。它比SimpleDateFormat更安全且更快速。它也使用與SimpleDateFormat相同的格式/分析模式規範。

+0

它只能用於3.2+而不是3.x – Wisteso 2017-07-07 16:23:39

1

另一種選擇是保持實例在一個線程安全的隊列:

import java.util.concurrent.ArrayBlockingQueue; 
private static final int DATE_FORMAT_QUEUE_LEN = 4; 
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; 
private ArrayBlockingQueue<SimpleDateFormat> dateFormatQueue = new ArrayBlockingQueue<SimpleDateFormat>(DATE_FORMAT_QUEUE_LEN); 
// thread-safe date time formatting 
public String format(Date date) { 
    SimpleDateFormat fmt = dateFormatQueue.poll(); 
    if (fmt == null) { 
     fmt = new SimpleDateFormat(DATE_PATTERN); 
    } 
    String text = fmt.format(date); 
    dateFormatQueue.offer(fmt); 
    return text; 
} 
public Date parse(String text) throws ParseException { 
    SimpleDateFormat fmt = dateFormatQueue.poll(); 
    if (fmt == null) { 
     fmt = new SimpleDateFormat(DATE_PATTERN); 
    } 
    Date date = null; 
    try { 
     date = fmt.parse(text); 
    } finally { 
     dateFormatQueue.offer(fmt); 
    } 
    return date; 
} 

dateFormatQueue的大小應該是接近它可以在同一時間通常調用這個函數的線程的估計數目。 在最壞的情況下,比這個數字多的線程實際上併發地使用所有的實例,一些SimpleDateFormat實例將被創建,因爲它已滿而無法返回到dateFormatQueue。這不會產生錯誤,只會造成創建僅使用一次的SimpleDateFormat的懲罰。

11

如果您使用的是Java 8中,您可能需要使用java.time.format.DateTimeFormatter

這個類是不可變的和線程安全的。

如:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); 
String str = new java.util.Date().toInstant() 
           .atZone(ZoneId.systemDefault()) 
           .format(formatter); 
0

我只是選擇3實現這一點,但做了一些更改代碼:

  • 的ThreadLocal通常應該是靜態
  • 似乎清潔覆蓋初值( )而不是測試if(get()== null)
  • 您可能想要設置語言環境和時間z一個除非你真的想要的默認設置(默認值是非常容易出錯的Java)

    private static final ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() { 
        @Override 
        protected SimpleDateFormat initialValue() { 
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh", Locale.US); 
         sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); 
         return sdf; 
        } 
    }; 
    public String formatDate(Date d) { 
        return tl.get().format(d); 
    }