2016-05-30 17 views
2

Ich konnte keine passende Lösung im Web finden, daher dachte ich mir zu fragen, ob meine Art der Verwendung eines Java-Formats korrekt ist.Thread-sicheres dynamisches Muster für NumberFormat/DecimalFormat

1) In der NumberFormat.java Dokumentation heißt es, dass

Zahlenformate werden in der Regel nicht synchronisiert. Es wird empfohlen, separate Format-Instanzen für jeden Thread zu erstellen.

Wir haben Formatobjekte (statisch initialisiert) in einer Multithread-Umgebung ohne Probleme verwendet. Ist es vielleicht, weil, sobald die Formate definiert sind, wir ihren Zustand nicht geändert werden (dh, keine Setter werden danach aufgerufen)

2) Ich muss jetzt ein neues Format definieren, das entweder eine oder zwei signifikante Ziffern nach Komma ausgeben sollte , abhängig von einer zusätzlichen Logik. Die Art, wie ich es getan habe, war, einen neuen Format-Wrapper zu definieren und auf zwei unterschiedliche DecimalFormat zu delegieren, abhängig vom Fall in der überschriebenen Methode #format (double, StringBuffer, FieldPosition). Hier ist der Code dafür:

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); 
     } 
    } 
}; 

Ist es die beste Praxis? Ich habe Bedenken, die Wrapperklasse nicht zu verwenden (sie dient nur dazu, der NumberFormat-Schnittstelle zu entsprechen und alle Arbeiten an inneren Formaten zu delegieren). Ich möchte DecimalFormat # applyPattern() nicht aufrufen, da dies die flüchtige Nebenläufigkeit kompromittieren würde.

Dank

+1

Es ist nicht empfehlenswert, NumberFormat-Instanzen über mehrere Threads hinweg zu verwenden/zu teilen. Obwohl Sie bis jetzt kein Problem hatten, werden sie auftreten, sobald Ihre Anwendung eine echte gleichzeitige Verarbeitung erreicht hat. Wir hatten einen ähnlichen Anwendungsfall für DateFormat, wo statisch erzeugte Datumsformate verwendet wurden, aber wir skalierten unsere Anwendung für riesige Datenverarbeitung, wo n-gleichzeitige Prozesse die Daten aufwirbeln, fanden wir, dass Format Ihnen sehr seltsame und unerwartete Ergebnisse geben kann. Sie können nicht fehlschlagen, aber erhalten einen unerwarteten Wert –

+0

@SangramJadhav, so dass Sie wahrscheinlich einen wiederverwendbaren Thread-Pool von Formaten behalten, richtig? – d56

+1

Wenn Sie das Format freigeben möchten, müssen Sie eine eigene Synchronisierung bereitstellen. Oder Sie können ThreadLocal verwenden, um eine Formatinstanz zu deklarieren. Auf diese Weise erhält jeder Thread eine eigene Kopie des Formatierungsprogramms und Sie müssen keine Synchronisierung bereitstellen. –

Antwort

3
  1. Wir Format Objekte verwendet haben (statisch initialisiert) in einer Multithreadumgebung bisher ohne Probleme. Ist es vielleicht, weil einmal die Formate definiert sind, wir ihren Zustand nicht verändert wird (dh es werden keine Setter genannt danach)

Es ist unmöglich, genau zu sagen, warum Sie keine Probleme gesehen haben, da wir Ich weiß nicht genau, wie du sie benutzt.Aus der Spitze von meinem Kopf, könnten ein paar Gründe:

  • Sie sind keine der Codepfade in DecimalFormat schlagen, die die veränderbaren Instanzvariablen verwenden;
  • Sie verwenden "zufällig" gegenseitigen Ausschluss, sodass Sie die Instanz nie in mehreren Threads gleichzeitig verwenden.
  • Sie verwenden eine Implementierung, die tatsächlich korrekt synchronisiert (beachten Sie die Javadoc sagt "im Allgemeinen nicht synchronisiert", nicht "nie synchronisiert");
  • Sie tatsächlich sind mit Problemen, aber Sie sind nur nicht ausreichend überwachen;
  • usw.

Die Sache über Synchronisierungsprobleme, wie ich jemand anderen Kommentar gestern gesehen haben, ist, dass Sie nicht um ein Problem zu sehen sind garantiert, wenn Sie nicht synchronisieren; Es ist nur so, dass Sie nicht garantiert sind, sie auch nicht zu sehen.

Der Punkt ist, dass, wenn Sie keine Synchronisation anwenden, Sie der Laune und der Gnade einer Anzahl von subtilen Änderungen sind, denen Sie möglicherweise nur völlig nicht bewusst sind. Heute funktioniert es, morgen nicht; Sie werden einen allmächtigen Job haben, warum.

  1. Ist es die beste Praxis?

Es gibt ein paar Probleme, die ich von hier denken kann:

  1. durch die Klasse erstreckt, riskieren Sie fallen Foul des fragile base class problem.

    Auf den Punkt gebracht, es sei denn, Sie tatsächlich die public StringBuffer format(double, StringBuffer, java.text.FieldPosition) Methode auf Ihrem DecimalFormat Instanz explizit aufrufen, können Sie nicht sicher wissen, ob Ihr überschriebene Methode ist eigentlich derjenige genannt: eine Änderung bei der Implementierung der Basisklasse (DecimalFormat) könnte die Logik ändern, auf die Sie angewiesen sind, diese Methode aufzurufen.

  2. Sie haben drei wandelbare Instanzen - FORMAT, DECIMAL_FORMAT und DECIMAL_FORMAT_DIGIT - die alle Arten von Setter haben, ihr Verhalten zu ändern.

    Sie sollten propagieren alle diese Setter zu allen Instanzen, damit sie konsistent verhalten, z. Wenn Sie setPositivePrefix unter FORMAT anrufen, sollten Sie auch die gleiche Methode unter DECIMAL_FORMAT und DECIMAL_FORMAT_DIGIT aufrufen.

Es sei denn, Sie tatsächlich FORMAT als Parameter an eine Methode übergeben müssen, wäre es sehr viel robuster sein, wenn Sie gerade eine einfache alte Methode definiert, die Ihre Logik ruft: im Grunde, bewegen Sie die Methode, die Sie aus zwingenden werden die anonyme Unterklasse:

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

Dann Sie diese Methode Aufruf explizit haben, wenn Sie diese spezielle Logik verwenden möchten.

+0

) 1. Ich glaube, Sie irren sich damit. Die NumberFormat # Format (Doppel) -Methode, die das DecimalFormat erstreckt, ist markiert Das bedeutet für mich, dass die Ersteller es gewünscht haben, dass die Konsumenten das andere Methodenformat überschreiben (Double, StringBuffer, FieldPosition) 2. Ich rufe keine Setter auf dem resultierenden Format auf. Es wird auch nicht threadsicher sein (na ja, noch mehr als es jetzt ist) 3. Ich kann das formatWithSpeicalLogic nicht aus der Klasse herausziehen, weil das Format (double) implizit von javax.swing aufgerufen wird. text.NumberFormatter Klasse – d56

+1

1. "Ich denke, du liegst falsch", nicht falsch, ju Vielleicht zu pessimistisch; Das FBCP ist eine unausweichliche Konsequenz von 'extends'. Du denkst einfach, dass es nicht passieren wird. Und wahrscheinlich wird es nicht; Ich würde das Haus nicht darauf wetten. 2. "Ich nenne keine Setter" Und können Sie garantieren, dass Sie es nie tun werden? 3) Das ist ein Detail, das Sie nicht in Frage stellen. –

1

ich nicht zu kommentieren den Ruf haben, aber in Bezug auf Punkt 2, der größte Nachteil ich sehe, ist, dass Sie das gesamte Verhalten der DecimalFormat Klasse nicht außer Kraft gesetzt haben, so dass Ihre resultierende Instanz wird sich inkonsistent verhalten. Zum Beispiel:

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()); 

ergibt

0.11 
0.111 

Es wäre besser, alle notwendigen Methoden zu überschreiben, wenn Sie diesen Weg gehen, so dass Sie eine konsistentes Verhalten zu bekommen. Alternativ könnten Sie eine Funktion in dem Thread speichern, die die Logik, die Du kapselt:

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

Das Problem ist, dass ich nur ein Format-Objekt brauche, das die ganze Logik erledigt. Der Grund hierfür ist, dass javax.swing.text.NumberFormatter ein java.text.NumberFormat-Objekt in seinem Konstruktor erwartet. Deshalb ging ich für die Vererbung, anstatt der Zusammensetzung. Ich habe die übrigen Methoden nicht überschrieben, da wir nur an den Doppeleingaben interessiert sind. Allerdings bin ich etwas besorgt über die peinliche resultierende Vererbung - 2 NumberFormats innerhalb der dritten; ( – d56

1

Da Sie sagen, dass Sie eine Instanz von NumberFormat haben müssen, würde ich vorschlagen, dass Sie diese Klasse erweitern und die erforderlichen Methoden zu implementieren, anstatt DecimalFormat erstrecken, die nicht für die Vererbung gedacht war:

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); 
    } 
}; 

Dies reduziert das fragile Basisklassenproblem, weil wir eine Basisklasse verwenden, die vererbt werden soll. Aber es gibt immer noch das Problem aller toten Konfigurationsmethoden. Im Idealfall würden Sie sie überschreiben, um entweder ihre Änderungen zu propagieren oder eine Ausnahme auszulösen.