2016-05-30 88 views
2

在網絡中找不到合適的解決方案,因此我想問問使用java格式的方式是否正確。NumberFormat/DecimalFormat的線程安全動態模式

1)在NumberFormat.java文檔它說,

數字格式通常不同步。建議爲每個線程創建單獨的格式實例。

我們一直在多線程環境中使用格式化對象(靜態初始化),目前沒有問題。這可能是因爲一旦格式被定義,我們的狀態就不會改變(即之後沒有調用者)

2)現在我需要定義一個新格式,它應該在逗號後輸出一個或兩個有效數字,這取決於一些額外的邏輯。我這樣做的方式是定義一個新的格式包裝和委託給兩個不同的DecimalFormat取決於在重寫的#format(double,StringBuffer,FieldPosition)方法中的情況。以下是該代碼:

private final NumberFormat FORMAT = new DecimalFormat() { 
    private final NumberFormat DECIMAL_FORMAT = new DecimalFormat("0.##"); 
    private final NumberFormat DECIMAL_FORMAT_DIGIT = new DecimalFormat(
        "0.0#"); 
    public StringBuffer format(double number, StringBuffer result, java.text.FieldPosition fieldPosition) { 
     if ((number >= 10 && Math.ceil(number) == number)) { 
      return DECIMAL_FORMAT.format(number, result, fieldPosition); 
     } else { 
      return DECIMAL_FORMAT_DIGIT.format(number, result, fieldPosition); 
     } 
    } 
}; 

這是最佳實踐嗎?我擔心沒有實際使用包裝類(它僅用於遵從NumberFormat接口並委託內部格式的所有工作)。我不想調用DecimalFormat#applyPattern(),因爲我認爲這會破壞易失性併發性。

感謝

+1

跨多個線程使用/共享NumberFormat實例是不好的做法。即使您現在沒有遇到任何問題,但是一旦您的應用程序實現了併發處理,它們就會發生。對於使用靜態創建的日期格式的DateFormat,我們有類似的用例,但是我們將應用程序的規模擴大到大量數據處理,其中n個併發進程正在生成數據,我們發現這種格式會給您帶來非常奇怪和意想不到的結果。他們可能不會失敗,但會給出一些意想不到的價值 –

+0

@SangramJadhav,所以你可能保留一個可重用的線程池格式,對吧? – d56

+1

如果你想分享格式,你需要提供我們自己的同步。或者你可以使用ThreadLocal來聲明格式化實例,這樣每個線程都有自己的格式化副本,而且你不必提供同步。 –

回答

3
  1. 我們已經使用格式對象(靜態初始化)在多線程環境中已經沒有問題爲止。難道也許因爲一旦格式定義,我們自己的狀態沒有改變(即沒有setters是後來被稱爲)

這是很難說究竟爲什麼你還沒有看到任何問題,因爲我們不知道你是如何使用它們的。關閉我的頭頂,有幾個可能的原因有:

  • 你是不是打在DecimalFormat它使用可變實例變量的任何代碼路徑;
  • 你是「巧合」應用互斥,所以你永遠不會在一個以上的線程中使用實例;
  • 你正在使用實際上確實同步的實現(注意Javadoc說「通常不同步」,而不是「從不同步」);
  • 你實際上有問題,但你只是沒有充分監測它們;

關於同步問題的事情,因爲我看到別人的評論,昨天是你不能保證看到一個問題,如果你不同步;只是你不能保證不會看到他們。

問題是,如果你沒有應用同步,那麼你就會隨時隨地發生任何細微的變化,你可能完全沒有意識到。今天它有效,明天它不會;你會有一個全能的工作,找出原因。

  1. 這是最佳做法嗎?

有幾個問題我能想到的在這裏:在fragile base class problem

  1. 通過擴展類,你可能會下降犯規。

    簡而言之,除非你實際上是調用你的DecimalFormat實例public StringBuffer format(double, StringBuffer, java.text.FieldPosition)方法明確,就不能可靠地知道你重寫的方法是否實際上是一個叫:改變基類的實現(DecimalFormat)可能會改變您所依賴的邏輯來調用該方法。

  2. 您有三個可變實例 - FORMAT,DECIMAL_FORMATDECIMAL_FORMAT_DIGIT - 它們有各種設置方法來改變它們的行爲。

    應該將所有這些設置器傳播到所有的實例,以便它們的行爲保持一致,例如,如果您在FORMAT上致電setPositivePrefix,則還應在DECIMAL_FORMATDECIMAL_FORMAT_DIGIT上調用相同的方法。

除非你確實需要通過FORMAT作爲參數的方法,這將是更強大的,如果你只是定義了調用你的邏輯一個普通的老方法:基本上,將你覆蓋了的方法匿名子類:

private static StringBuffer formatWithSpecialLogic(double number, StringBuffer result, java.text.FieldPosition fieldPosition) { 

然後你如果你要使用特殊的邏輯必須調用方法作了明確規定。

+0

)1.我認爲你錯了,DecimalFormat擴展的NumberFormat#format(double)方法被標記這意味着創建者已經希望消費者重寫其他方法格式(double,StringBuffer,FieldPosition)。 2.我不會在結果格式上調用任何setter。它也將不是線程安全的(當然,甚至比現在更多) 3.我無法將formatWithSpeicalLogic拉出類,因爲格式(double)是由javax.swing隱式調用的。 text.NumberFormatter class – d56

+1

1.「我認爲你錯了」沒錯,ju或許過於悲觀; FBCP是'extend'的一個不可避免的結果。你只是認爲它不會發生。而且,可能它不會;我只是不會賭上它。 2.「我不打電話給任何人」你能保證你永遠不會? 3)這是你忽略了這個問題的一個細節。 –

1

我沒有信譽發表評論,但在關於第2點,我看到的最大的缺點是你還沒有覆蓋所有的DecimalFormat類的行爲,所以你的結果實例將表現不一致。例如:

final StringBuffer buffer = new StringBuffer(); 
FORMAT.format(0.111d, buffer, new FieldPosition(0)); 
System.out.println(buffer.toString()); 

final StringBuffer buffer2 = new StringBuffer(); 
FORMAT.format(new BigDecimal(0.111d), buffer2, new FieldPosition(0)); 
System.out.println(buffer2.toString()); 

產生

0.11 
0.111 

這將是更好的覆蓋了所有必要的方法,如果你要這條路線讓你獲得一致的行爲。另外,您可以存儲在ThreadLocal的一個功能,它封裝所需的邏輯:

private final ThreadLocal<Function<Double, String>> DECIMAL_FORMATTER = new ThreadLocal<Function<Double, String>>() { 
    @Override 
    protected Function<Double, String> initialValue() { 
    final DecimalFormat decimalFormat = new DecimalFormat("0.##"); 
    final DecimalFormat decimalFormatDigit = new DecimalFormat("0.0#"); 
    return (number) -> { 
     if ((number >= 10 && Math.ceil(number) == number)) { 
     return decimalFormat.format(number); 
     } else { 
     return decimalFormatDigit.format(number); 
     } 
    }; 
    } 
}; 

System.out.println(DECIMAL_FORMATTER.get().apply(0.111d)); 
+0

問題是,我只需要一個執行所有邏輯的格式化對象。原因是javax.swing.text.NumberFormatter在其構造函數中需要一個java.text.NumberFormat對象。 因此,我去了繼承,而不是組成。我沒有覆蓋剩下的方法,因爲我們只對雙重輸入感興趣。但是,我對第三個函數的尷尬結果略有擔心 - 2 NumberFormats( – d56

1

既然你說你需要有NumberFormat一個實例,我建議你繼承了該類和實現所需要的方法,而不是延長DecimalFormat這是不是爲了繼承:

private final NumberFormat FORMAT = new NumberFormat() { 
    private final NumberFormat DECIMAL_FORMAT = new DecimalFormat("0.##"); 
    private final NumberFormat DECIMAL_FORMAT_DIGIT = new DecimalFormat("0.0#"); 

    @Override 
    public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) { 
     if ((number >= 10 && Math.ceil(number) == number)) { // or number % 1 == 0 
      return DECIMAL_FORMAT.format(number, result, fieldPosition); 
     } else { 
      return DECIMAL_FORMAT_DIGIT.format(number, result, fieldPosition); 
     } 
    } 

    @Override 
    public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) { 
     return format((double)number, result, fieldPosition); 
    } 

    @Override 
    public Number parse(String source, ParsePosition parsePosition) { 
     return DECIMAL_FORMAT.parse(source, parsePosition); 
    } 
}; 

這降低脆弱的基類問題,因爲我們正在使用一個基本類,這意味着要被繼承。但是仍然存在所有死亡配置方法的問題。理想情況下,您可以覆蓋它們以傳播其更改或拋出一些異常。