Sincronizzazione dell'accesso a SimpleDateFormat


90

Il javadoc per SimpleDateFormat indica che SimpleDateFormat non è sincronizzato.

"I formati della data non sono sincronizzati. Si consiglia di creare istanze di formato separate per ogni thread. Se più thread accedono a un formato contemporaneamente, è necessario sincronizzarlo esternamente."

Ma qual è l'approccio migliore per utilizzare un'istanza di SimpleDateFormat in un ambiente con più thread. Ecco alcune opzioni a cui ho pensato, ho utilizzato le opzioni 1 e 2 in passato, ma sono curioso di sapere se ci sono alternative migliori o quale di queste opzioni offrirebbe le migliori prestazioni e concorrenza.

Opzione 1: creare istanze locali quando necessario

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

Opzione 2: creare un'istanza di SimpleDateFormat come variabile di classe ma sincronizzare l'accesso ad essa.

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

Opzione 3: creare un ThreadLocal per archiviare un'istanza diversa di SimpleDateFormat per ogni thread.

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

10
+1 per aver sollevato questo problema. Così tante persone pensano che SimpleDateFormat sia thread-safe (vedo ipotesi ovunque).
Adam Gent

Per ulteriori informazioni sull'approccio ThreadLocal, vedere: javaspecialists.eu/archive/Issue172.html
miner49r

E per il motivo, vedi questa domanda: stackoverflow.com/questions/6840803/…
Raedwald

@ 3urdoch Hai ignorato per errore la parola chiave "statica" nell'opzione-2?
M Faisal Hameed

Risposte:


43
  1. La creazione di SimpleDateFormat è costosa . Non usarlo a meno che non sia fatto di rado.

  2. OK se riesci a convivere con un po 'di blocco. Utilizzare se formatDate () non è molto utilizzato.

  3. Opzione più veloce SE si riutilizzano i thread ( pool di thread ). Utilizza più memoria di 2. e ha un overhead di avvio più elevato.

Per le applicazioni sia 2. che 3. sono opzioni valide. Quale è il migliore per il tuo caso dipende dal tuo caso d'uso. Attenzione all'ottimizzazione prematura. Fallo solo se ritieni che questo sia un problema.

Per le librerie che verrebbero utilizzate da terze parti, utilizzerei l'opzione 3.


Se usiamo l'opzione 2 e dichiariamo SimpleDateFormatcome variabile di istanza, possiamo usarla synchronized blockper renderla thread-safe. Ma il sonar mostra l'avvertimento squid-AS2885 . C'è un modo per risolvere il problema del sonar?
M Faisal Hameed

24

L'altra opzione è Commons Lang FastDateFormat ma puoi usarla solo per la formattazione della data e non per l'analisi.

A differenza di Joda, può funzionare come un sostituto immediato per la formattazione. (Aggiornamento: dalla v3.3.2, FastDateFormat può produrre un FastDateParser , che è un sostituto thread-safe drop-in per SimpleDateFormat)


8
A partire da Commons Lang 3.2, FastDateFormatha anche il parse()metodo
manuna

19

Se stai usando Java 8, potresti voler usare java.time.format.DateTimeFormatter:

Questa classe è immutabile e thread-safe.

per esempio:

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

6

Commons Lang 3.x ora ha FastDateParser e FastDateFormat. È thread-safe e più veloce di SimpleDateFormat. Utilizza anche le stesse specifiche di formato / modello di analisi di SimpleDateFormat.


È disponibile solo in 3.2+ e non 3.x
Wisteso

4

Non usare SimpleDateFormat, usa invece DateTimeFormatter di joda-time. È un po 'più rigoroso dal punto di vista dell'analisi e quindi non è proprio un calo in sostituzione di SimpleDateFormat, ma joda-time è molto più amichevole in termini di sicurezza e prestazioni.


3

Direi, creare una semplice classe wrapper per SimpleDateFormat che sincronizza l'accesso a parse () e format () e può essere utilizzata come sostituzione drop-in. Più infallibile della tua opzione n. 2, meno ingombrante della tua opzione n. 3.

Sembra che rendere SimpleDateFormat non sincronizzato sia stata una decisione di progettazione sbagliata da parte dei progettisti dell'API Java; Dubito che qualcuno si aspetti che format () e parse () debbano essere sincronizzati.


1

Un'altra opzione è mantenere le istanze in una coda thread-safe:

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

La dimensione di dateFormatQueue dovrebbe essere qualcosa di simile al numero stimato di thread che possono chiamare regolarmente questa funzione allo stesso tempo. Nel caso peggiore in cui più thread di questo numero utilizzano effettivamente tutte le istanze contemporaneamente, verranno create alcune istanze SimpleDateFormat che non possono essere restituite a dateFormatQueue perché è pieno. Questo non genererà un errore, incorrerà solo nella penalità di creare alcuni SimpleDateFormat che vengono utilizzati una sola volta.


1

L'ho appena implementato con l'opzione 3, ma ho apportato alcune modifiche al codice:

  • ThreadLocal dovrebbe solitamente essere statico
  • Sembra più corretto sovrascrivere initialValue () piuttosto che testare if (get () == null)
  • Potresti voler impostare la lingua e il fuso orario a meno che tu non voglia veramente le impostazioni predefinite (le impostazioni predefinite sono molto soggette a errori con Java)

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

0

Immagina che la tua applicazione abbia un thread. Perché sincronizzare quindi l'accesso alla variabile SimpleDataFormat?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.