Synchroniser l'accès à SimpleDateFormat

90

Le javadoc pour SimpleDateFormat indique que SimpleDateFormat n'est pas synchronisé.

"Les formats de date ne sont pas synchronisés. Il est recommandé de créer des instances de format distinctes pour chaque thread. Si plusieurs threads accèdent à un format simultanément, il doit être synchronisé en externe."

Mais quelle est la meilleure approche pour utiliser une instance de SimpleDateFormat dans un environnement multithread. Voici quelques options auxquelles j'ai pensé, j'ai utilisé les options 1 et 2 dans le passé, mais je suis curieux de savoir s'il existe de meilleures alternatives ou laquelle de ces options offrirait les meilleures performances et la meilleure concurrence.

Option 1: créer des instances locales si nécessaire

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

Option 2: créez une instance de SimpleDateFormat en tant que variable de classe mais synchronisez l'accès à celle-ci.

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

Option 3: créez un ThreadLocal pour stocker une instance différente de SimpleDateFormat pour chaque 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);
}
3urdoch
la source
10
+1 pour avoir soulevé ce problème. Tant de gens pensent que SimpleDateFormat est thread-safe (je vois des hypothèses partout).
Adam Gent
Pour plus d'informations sur l'approche ThreadLocal, voir: javaspecialists.eu/archive/Issue172.html
miner49r
Et pour savoir pourquoi, voyez cette question: stackoverflow.com/questions/6840803
...
@ 3urdoch Avez-vous ignoré par erreur le mot-clé "statique" dans l'Option-2?
M Faisal Hameed

Réponses:

43
  1. La création de SimpleDateFormat coûte cher . N'utilisez pas ceci à moins que ce ne soit fait rarement.

  2. OK si vous pouvez vivre avec un peu de blocage. À utiliser si formatDate () n'est pas beaucoup utilisé.

  3. Option la plus rapide SI vous réutilisez des threads ( pool de threads ). Utilise plus de mémoire que 2. et a une surcharge de démarrage plus élevée.

Pour les applications 2. et 3. sont des options viables. Ce qui convient le mieux à votre cas dépend de votre cas d'utilisation. Méfiez-vous des optimisations prématurées. Ne le faites que si vous pensez que c'est un problème.

Pour les bibliothèques qui seraient utilisées par des tiers, j'utiliserais l'option 3.

Peter Knego
la source
Si nous utilisons Option-2 et déclarons SimpleDateFormatcomme variable d'instance, nous pouvons l'utiliser synchronized blockpour la rendre thread-safe. Mais le sonar affiche un avertissement squid-AS2885 . Existe-t-il un moyen de résoudre le problème du sonar?
M Faisal Hameed
24

L'autre option est Commons Lang FastDateFormat, mais vous ne pouvez l'utiliser que pour le formatage de la date et non l'analyse.

Contrairement à Joda, il peut fonctionner comme un remplacement instantané pour le formatage. (Mise à jour: depuis la v3.3.2, FastDateFormat peut produire un FastDateParser , qui est un remplacement de thread-safe pour SimpleDateFormat)

Adam Gent
la source
8
Depuis Commons Lang 3.2, FastDateFormata également une parse()méthode
manuna
19

Si vous utilisez Java 8, vous souhaiterez peut-être utiliser java.time.format.DateTimeFormatter:

Cette classe est immuable et thread-safe.

par exemple:

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

Commons Lang 3.x a maintenant FastDateParser ainsi que FastDateFormat. Il est thread-safe et plus rapide que SimpleDateFormat. Il utilise également les mêmes spécifications de format / modèle d'analyse que SimpleDateFormat.

Chas
la source
Il est uniquement disponible en version 3.2+ et non 3.x
Wisteso
4

N'utilisez pas SimpleDateFormat, utilisez plutôt le DateTimeFormatter de joda-time. Il est un peu plus strict du côté de l'analyse et ne remplace donc pas SimpleDateFormat, mais joda-time est beaucoup plus convivial en termes de sécurité et de performances.

Jed Wesley-Smith
la source
3

Je dirais, créez une simple classe wrapper pour SimpleDateFormat qui synchronise l'accès à parse () et format () et peut être utilisée comme remplacement instantané. Plus infaillible que votre option n ° 2, moins lourde que votre option n ° 3.

On dirait que rendre SimpleDateFormat non synchronisé était une mauvaise décision de conception de la part des concepteurs d'API Java; Je doute que quiconque s'attende à ce que format () et parse () aient besoin d'être synchronisés.

Andy
la source
1

Une autre option consiste à conserver les instances dans une file d'attente 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 taille de dateFormatQueue doit être quelque chose de proche du nombre estimé de threads qui peuvent régulièrement appeler cette fonction en même temps. Dans le pire des cas où plus de threads que ce nombre utilisent réellement toutes les instances simultanément, certaines instances SimpleDateFormat seront créées et ne pourront pas être renvoyées à dateFormatQueue car elle est pleine. Cela ne générera pas d'erreur, cela entraînera simplement la pénalité de créer un SimpleDateFormat qui ne sera utilisé qu'une seule fois.

SamJ
la source
1

Je viens de l'implémenter avec l'option 3, mais j'ai apporté quelques modifications au code:

  • ThreadLocal doit généralement être statique
  • Il semble plus propre de remplacer initialValue () plutôt que de tester if (get () == null)
  • Vous pouvez définir les paramètres régionaux et le fuseau horaire à moins que vous ne souhaitiez vraiment les paramètres par défaut (les paramètres par défaut sont très sujets aux erreurs avec 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);
    }
mwk
la source
0

Imaginez que votre application ait un thread. Pourquoi synchroniseriez-vous l'accès à la variable SimpleDataFormat alors?

Vortex
la source