Synchronisieren des Zugriffs auf SimpleDateFormat

90

Das Javadoc für SimpleDateFormat gibt an, dass SimpleDateFormat nicht synchronisiert ist.

"Datumsformate werden nicht synchronisiert. Es wird empfohlen, separate Formatinstanzen für jeden Thread zu erstellen. Wenn mehrere Threads gleichzeitig auf ein Format zugreifen, muss es extern synchronisiert werden."

Was ist jedoch der beste Ansatz für die Verwendung einer Instanz von SimpleDateFormat in einer Umgebung mit mehreren Threads? Hier sind einige Optionen, an die ich gedacht habe: Ich habe in der Vergangenheit die Optionen 1 und 2 verwendet, bin aber gespannt, ob es bessere Alternativen gibt oder welche dieser Optionen die beste Leistung und Parallelität bieten.

Option 1: Erstellen Sie bei Bedarf lokale Instanzen

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

Option 2: Erstellen Sie eine Instanz von SimpleDateFormat als Klassenvariable, synchronisieren Sie jedoch den Zugriff darauf.

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

Option 3: Erstellen Sie ein ThreadLocal, um für jeden Thread eine andere Instanz von SimpleDateFormat zu speichern.

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);
}
3urdoch
quelle
10
+1 für das Aufwerfen dieses Problems. So viele Leute denken, dass SimpleDateFormat threadsicher ist (ich sehe überall Annahmen).
Adam Gent
Weitere Informationen zum ThreadLocal-Ansatz finden Sie unter: javaspecialists.eu/archive/Issue172.html
mineral49r
Und warum, siehe diese Frage: stackoverflow.com/questions/6840803/…
Raedwald
@ 3urdoch Haben Sie in Option 2 fälschlicherweise das Schlüsselwort 'static' übersprungen?
M Faisal Hameed

Antworten:

43
  1. Das Erstellen von SimpleDateFormat ist teuer . Verwenden Sie dies nur, wenn es selten gemacht wird.

  2. OK, wenn Sie mit ein bisschen Blockierung leben können. Verwenden Sie diese Option, wenn formatDate () nicht häufig verwendet wird.

  3. Schnellste Option, wenn Sie Threads wiederverwenden ( Thread-Pool ). Verwendet mehr Speicher als 2. und hat einen höheren Startaufwand.

Für Anwendungen sind sowohl 2. als auch 3. praktikable Optionen. Welches für Ihren Fall am besten geeignet ist, hängt von Ihrem Anwendungsfall ab. Vorsicht vor vorzeitiger Optimierung. Tun Sie es nur, wenn Sie glauben, dass dies ein Problem ist.

Für Bibliotheken, die von Drittanbietern verwendet werden, würde ich Option 3 verwenden.

Peter Knego
quelle
Wenn wir Option-2 verwenden und SimpleDateFormatals Instanzvariable deklarieren, können wir synchronized blocksie threadsicher machen. Aber Sonar zeigt Warnung Tintenfisch-AS2885 . Gibt es eine Möglichkeit, das Sonarproblem zu lösen?
M Faisal Hameed
24

Die andere Option ist Commons Lang FastDateFormat , Sie können sie jedoch nur zur Datumsformatierung und nicht zum Parsen verwenden.

Im Gegensatz zu Joda kann es als Ersatz für die Formatierung dienen. (Update: Seit Version 3.3.2 kann FastDateFormat einen FastDateParser erstellen , der einen thread-sicheren Ersatz für SimpleDateFormat darstellt.)

Adam Gent
quelle
8
Seit Commons Lang 3.2, FastDateFormathat auch parse()Methode
Manuna
19

Wenn Sie Java 8 verwenden, möchten Sie möglicherweise verwenden java.time.format.DateTimeFormatter :

Diese Klasse ist unveränderlich und threadsicher.

z.B:

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

Commons Lang 3.x verfügt jetzt sowohl über FastDateParser als auch über FastDateFormat. Es ist threadsicher und schneller als SimpleDateFormat. Es werden auch dieselben Format- / Analysemusterspezifikationen wie bei SimpleDateFormat verwendet.

Chas
quelle
Es ist nur in 3.2+ und nicht in 3.x verfügbar
Wisteso
4

Verwenden Sie nicht SimpleDateFormat, sondern stattdessen DateTimeFormatter von joda-time. Auf der Parsing-Seite ist es etwas strenger und daher kein Ersatz für SimpleDateFormat, aber die Joda-Zeit ist in Bezug auf Sicherheit und Leistung viel freundlicher.

Jed Wesley-Smith
quelle
3

Ich würde sagen, erstellen Sie eine einfache Wrapper-Klasse für SimpleDateFormat, die den Zugriff auf parse () und format () synchronisiert und als Drop-In-Ersatz verwendet werden kann. Narrensicherer als Ihre Option Nr. 2, weniger umständlich als Ihre Option Nr. 3.

Es scheint, als wäre es eine schlechte Entwurfsentscheidung der Java-API-Designer gewesen, SimpleDateFormat nicht synchronisiert zu machen. Ich bezweifle, dass jemand erwartet, dass format () und parse () synchronisiert werden müssen.

Andy
quelle
1

Eine weitere Option besteht darin, Instanzen in einer thread-sicheren Warteschlange zu halten:

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

Die Größe von dateFormatQueue sollte ungefähr der geschätzten Anzahl von Threads entsprechen, die diese Funktion routinemäßig gleichzeitig aufrufen können. Im schlimmsten Fall, wenn mehr Threads als diese Nummer tatsächlich alle Instanzen gleichzeitig verwenden, werden einige SimpleDateFormat-Instanzen erstellt, die nicht an dateFormatQueue zurückgegeben werden können, da sie voll sind. Dies erzeugt keinen Fehler, sondern verursacht nur die Strafe für das Erstellen von SimpleDateFormat, die nur einmal verwendet werden.

SamJ
quelle
1

Ich habe dies gerade mit Option 3 implementiert, aber einige Codeänderungen vorgenommen:

  • ThreadLocal sollte normalerweise statisch sein
  • Scheint sauberer zu sein, initialValue () zu überschreiben, anstatt zu testen, ob (get () == null)
  • Möglicherweise möchten Sie das Gebietsschema und die Zeitzone festlegen, es sei denn, Sie möchten wirklich die Standardeinstellungen festlegen (Standardeinstellungen sind bei Java sehr fehleranfällig).

    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);
    }
mwk
quelle
0

Stellen Sie sich vor, Ihre Anwendung hat einen Thread. Warum sollten Sie dann den Zugriff auf die Variable SimpleDataFormat synchronisieren?

Wirbel
quelle