2010-03-09 70 views
37

我收到一個發現錯誤錯誤 - 調用靜態java.text.DateFormat中和 我不知道爲什麼這不是好/最好低於在做這些事情的原因的方法。調用靜態java.text.DateFormat方法不可取?

private static final Date TODAY = Calendar.getInstance().getTime(); 
private static final DateFormat yymmdd = new SimpleDateFormat("yyMMdd"); 

private String fileName = "file_" + yymmdd.format(TODAY); 
+1

除了調用方法,這段代碼確實看起來有點懷疑 - 「TODAY」將是一個不變的日子,格式化程序是最終的非靜態的? – Pool 2010-03-09 14:30:51

+2

今天也不總是「今天」,如果例如這個類被加載,然後JVM被運行到第二天 - 任何依賴TODAY成爲當天的邏輯將不起作用,除非你是佔這種差異。 – Peter 2010-03-09 15:17:24

+0

@彼得 - 是的,程序正在爲每次運行重置。 – Joset 2010-03-09 15:19:35

回答

68

DateFormats不是線程安全的,這意味着它們保持狀態的內部表示。如果多個線程同時訪問同一個實例,在靜態環境中使用它們會產生一些非常奇怪的錯誤。

我的建議是將你的變量局部放在你使用它們的地方,而不是使它們成爲類的靜態屬性。它看起來就像你可能會做這個,當你初始化類,所以你可以做到這一點在構造函數:

public class MyClass { 
    private String fileName; 

    public MyClass() { 
     final Date today = Calendar.getInstance().getTime(); 
     final DateFormat yymmdd = new SimpleDateFormat("yyMMdd"); 

     this.fileName = "file_" + yymmdd.format(TODAY); 
    } 
    ... 
} 

如果你需要使用在多個地方格式化,你可能只是使圖案static final並在需要時創建新的本地DateFormat

public class MyClass { 
    private static final String FILENAME_DATE_PATTERN = "yyMMdd"; 

    public void myMethod() { 
     final DateFormat format = new SimpleDateFormat(FILENAME_DATE_PATTERN); 
     // do some formatting 
    } 
} 

FindBugs documentation的問題說:

正如Javadoc,是的DateFormats 固有不安全的多線程使用 。檢測器發現 的一個調用DateFormat的實例,該實例已通過靜態字段獲得 。這 看起來可疑。

欲瞭解更多信息,請參閱太陽 錯誤#6231579和太陽錯誤#6178997。

而且javadoc for DateFormat提示:

日期格式不同步。建議使用 爲每個線程創建單獨的 格式實例。如果多個線程同時訪問格式 ,則必須在外部同步 。

Jack Leow's answer對於「TODAY」的靜態使用的語義也有一個好的觀點。另外,我已經看到這種情況發生在高流量的生產環境中,首先要進行調試是一件非常令人困惑的事情;所以根據我的經驗,FindBugs警告實際上是一個有用的建議(與其他一些靜態分析規則不同,有時似乎太挑剔)。

+1

我不太清楚,我認爲每次調用myMethod()時創建一個SimpleDateFormat都太昂貴了。 – Joset 2010-03-09 15:31:38

+1

@eradicus - 您是否有性能指標證明它「太貴」?除非你已經注意到它是應用程序的瓶頸,否則我寧願採用本地方法來處理全局範圍的「static final」。強制Knuth:http:// c2。com/cgi/wiki?PrematureOptimization – 2010-03-09 15:53:56

+0

爲什麼變量不是「最終的」,即不變?如果它不能改變所有的線程應該能夠分享它,不是嗎?或者它是初始化競爭條件的問題? – kossmoboleat 2014-07-24 14:08:16

0

這不是線程安全的,一方面。

4

你確定這不是

private static final DateFormat yymmdd = new SimpleDateFormat("yyMMdd"); 

?這就是錯誤信息所表明的。

我認爲它的目標是DateFormat不是線程安全的,因此將實例作爲靜態字段表示潛在的競爭條件。

+0

你是對的。有一個DateFormat的靜態修飾符 – Joset 2010-03-09 14:30:30

0

我認爲這是因爲格式不是線程安全的?

(我還沒有看到什麼FindBugs的抱怨,你可以提供 警告文本?)

0

你能得到這個走開通過包裝所有引用的同步塊中的日期格式 - 只要確保所有的呼叫都包在同步對象!

+0

這可能會擺脫FindBugs錯誤,但是當你需要時新建一個本地DateFormat不是更合乎邏輯嗎? – 2010-03-09 14:52:40

+0

@Rob,我想這將取決於同步是否實際上可能在一段時間內阻塞一次,或者如果它只是一個安撫FindBugs的形式。 – 2010-03-09 15:27:01

+0

是的。我想如果創建一個新的DateFormat的開銷會影響系統性能,那麼同步可能是一種選擇,但我懷疑它在大多數情況下是否會發生。另外,如果是這樣的話,它可能會是一個更高負載的系統,我認爲同步會以類似的方式阻礙性能。 – 2010-03-09 15:31:06

4

我不確定FindBugs是否在抱怨這個問題,但我在代碼中看到的一個問題是,您將TODAY定義爲類級別(靜態),常量(最終)變量。這意味着你想要TODAY永遠不會改變(我不相信這是事實,因爲java.util.Dates是可變的,但這是另一回事)。

想想如果您的應用程序運行多天會發生什麼? TODAY(除非您更新它)將引用應用程序啓動的那一天,而不是當前日期。你確定這是你的意思嗎?

這可能不是您代碼中的錯誤,但意圖不明確,我相信可能是是FindBugs抱怨的。

+0

是的,今天一定不能改變。 – Joset 2010-03-09 15:11:32

12

Commons Lang有一個FastDateFormat線程安全的對象。 它只是格式化,而不是解析。

如果您可以使用commons-lang,這可能適合您。

private static final Date TODAY = Calendar.getInstance().getTime(); 
private static final FastDateFormat yymmdd = FastDateFormat.getInstance("yyMMdd"); 

private String fileName = "file_" + yymmdd.format(TODAY); 
2

未提及的替代方法是使用ThreadLocal。見http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html爲3個選項之間詳細信息+性能對比:

  • 創建實例每次
  • 同步訪問
  • 使用的ThreadLocal
  • 使用的ThreadLocal的

實施例:

private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() { 
    @Override 
    protected SimpleDateFormat initialValue() { 
     return new SimpleDateFormat("yyMMdd"); 
    } 
}; 

用法:

DATE_FORMAT.get().format(TODAY)