Synchronizowanie dostępu do SimpleDateFormat

91

Dokument javadoc dla SimpleDateFormat stwierdza, że ​​SimpleDateFormat nie jest zsynchronizowany.

„Formaty dat nie są synchronizowane. Zaleca się tworzenie oddzielnych wystąpień formatu dla każdego wątku. Jeśli wiele wątków jednocześnie uzyskuje dostęp do formatu, należy go synchronizować zewnętrznie”.

Ale jakie jest najlepsze podejście do korzystania z wystąpienia SimpleDateFormat w środowisku wielowątkowym. Oto kilka opcji, o których myślałem. W przeszłości korzystałem z opcji 1 i 2, ale jestem ciekawy, czy istnieją lepsze alternatywy lub która z tych opcji zapewniłaby najlepszą wydajność i współbieżność.

Opcja 1: w razie potrzeby utwórz wystąpienia lokalne

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

Opcja 2: Utwórz wystąpienie SimpleDateFormat jako zmienną klasową, ale zsynchronizuj do niej dostęp.

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

Opcja 3: Utwórz ThreadLocal, aby przechowywać różne wystąpienie SimpleDateFormat dla każdego wątku.

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
źródło
10
+1 za zgłoszenie tego problemu. Tak wiele osób uważa, że ​​SimpleDateFormat jest bezpieczny dla wątków (wszędzie widzę założenia).
Adam Gent
Aby uzyskać więcej informacji na temat podejścia ThreadLocal, zobacz: javaspecialists.eu/archive/Issue172.html
miner49r
A dlaczego, zobacz to pytanie: stackoverflow.com/questions/6840803/…
Raedwald
@ 3urdoch Czy omyłkowo pominięto „statyczne” słowo kluczowe w Opcji-2?
M Faisal Hameed

Odpowiedzi:

43
  1. Tworzenie SimpleDateFormat jest kosztowne . Nie używaj tego, chyba że robi się to rzadko.

  2. OK, jeśli możesz żyć z odrobiną blokowania. Użyj, jeśli formatDate () nie jest często używana.

  3. Najszybsza opcja, JEŚLI ponownie używasz wątków ( pula wątków ). Zużywa więcej pamięci niż 2. i ma większe obciążenie uruchamiania.

W przypadku zastosowań możliwe są opcje 2. i 3.. To, które jest najlepsze dla twojego przypadku, zależy od twojego przypadku użycia. Uważaj na przedwczesną optymalizację. Rób to tylko wtedy, gdy uważasz, że jest to problem.

W przypadku bibliotek, które byłyby używane przez firmę zewnętrzną, użyłbym opcji 3.

Peter Knego
źródło
Jeśli użyjemy Option-2 i zadeklarujemy SimpleDateFormatjako zmienną instancji, możemy użyć, synchronized blockaby uczynić ją bezpieczną dla wątków. Ale sonar pokazuje ostrzegawczą kałamarnicę-AS2885 . Czy istnieje sposób rozwiązania problemu z sonarem?
M Faisal Hameed
24

Inną opcją jest Commons Lang FastDateFormat, ale można jej używać tylko do formatowania daty, a nie do analizowania.

W przeciwieństwie do Jody, może pełnić funkcję zastępczego zamiennika formatowania. (Aktualizacja: od wersji 3.3.2 FastDateFormat może tworzyć FastDateParser , który jest bezpiecznym dla wątków zamiennikiem SimpleDateFormat)

Adam Gent
źródło
8
Od Commons Lang 3.2 FastDateFormatma również parse()metodę
manuna
20

Jeśli korzystasz z języka Java 8, możesz użyć java.time.format.DateTimeFormatter:

Ta klasa jest niezmienna i bezpieczna dla wątków.

na przykład:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new java.util.Date().toInstant()
                                 .atZone(ZoneId.systemDefault())
                                 .format(formatter);
Paul Vargas
źródło
6

Commons Lang 3.x ma teraz FastDateParser oraz FastDateFormat. Jest bezpieczny dla wątków i szybszy niż SimpleDateFormat. Używa również tych samych specyfikacji formatu / wzorców analizy co SimpleDateFormat.

Chas
źródło
Jest dostępny tylko w wersji 3.2+, a nie 3.x
Wisteso
4

Nie używaj SimpleDateFormat, zamiast tego użyj DateTimeFormatter joda-time. Jest nieco bardziej rygorystyczny po stronie parsowania, więc nie jest to spadek w zastępstwie SimpleDateFormat, ale czas joda jest znacznie bardziej przyjazny pod względem bezpieczeństwa i wydajności.

Jed Wesley-Smith
źródło
3

Powiedziałbym, że utwórz prostą klasę opakowującą dla SimpleDateFormat, która synchronizuje dostęp do parse () i format () i może być używana jako zamiennik typu drop-in. Bardziej niezawodny niż opcja nr 2, mniej uciążliwy niż opcja nr 3.

Wygląda na to, że niezsynchronizowanie SimpleDateFormat było kiepską decyzją projektową ze strony projektantów Java API; Wątpię, czy ktoś oczekuje, że format () i parse () będą musiały być zsynchronizowane.

Andy
źródło
1

Inną opcją jest przechowywanie instancji w kolejce bezpiecznej wątkowo:

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

Rozmiar dateFormatQueue powinien być zbliżony do szacowanej liczby wątków, które mogą rutynowo wywoływać tę funkcję w tym samym czasie. W najgorszym przypadku, gdy więcej wątków niż ta liczba faktycznie używa wszystkich instancji jednocześnie, zostaną utworzone niektóre instancje SimpleDateFormat, których nie można zwrócić do dateFormatQueue, ponieważ jest pełny. Nie spowoduje to błędu, po prostu pociągnie za sobą karę za utworzenie jakiegoś SimpleDateFormat, które są używane tylko raz.

SamJ
źródło
1

Właśnie zaimplementowałem to z Opcją 3, ale wprowadziłem kilka zmian w kodzie:

  • ThreadLocal zwykle powinien być statyczny
  • Wydaje się czystsze, aby zastąpić initialValue () zamiast testować if (get () == null)
  • Możesz chcieć ustawić lokalizację i strefę czasową, chyba że naprawdę chcesz ustawienia domyślne (ustawienia domyślne są bardzo podatne na błędy w Javie)

    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
źródło
0

Wyobraź sobie, że Twoja aplikacja ma jeden wątek. Dlaczego więc miałbyś synchronizować dostęp do zmiennej SimpleDataFormat?

Wir
źródło