web-dev-qa-db-fra.com

Synchroniser l'accès à SimpleDateFormat

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 simultanément à un format .__, celui-ci 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 simultanéité.

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);
}
84
3urdoch
  1. Créer SimpleDateFormat est coûteux . N'utilisez pas ceci sauf si c'est fait rarement.

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

  3. L'option la plus rapide SI vous réutilisez des threads ( pool de threads ). Utilise plus de mémoire que 2. et entraîne des frais de démarrage plus importants.

Pour les applications, les options 2. et 3. sont des options viables. La meilleure solution pour votre cas dépend de votre cas d'utilisation. Attention à l'optimisation prématurée. Ne le faites que si vous croyez que c'est un problème.

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

42
Peter Knego

Commons Lang FastDateFormat constitue l’autre option, mais vous ne pouvez l’utiliser que pour le formatage de la date et non pour son analyse. 

Contrairement à Joda, il peut remplacer le formatage directement par le remplacement . (Mise à jour: depuis la version 3.3.2, FastDateFormat peut produire un FastDateParser , qui constitue un remplacement sans danger pour les threads pour SimpleDateFormat)

24
Adam Gent

Si vous utilisez Java 8, vous voudrez 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);
13
Paul Vargas

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. 

6
Chas

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

4
Jed Wesley-Smith

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

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

3
Andy

Je viens d'implémenter cela avec Option 3, mais j'ai apporté quelques modifications de code:

  • ThreadLocal devrait normalement être statique
  • Semble nettoyeur pour remplacer initialValue () plutôt que de tester si (get () == null)
  • Vous voudrez peut-être définir les paramètres régionaux et le fuseau horaire à moins que vous ne souhaitiez réellement 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);
    }
    
1
mwk

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 proche du nombre estimé de threads pouvant appeler cette fonction en même temps, par la même occasion ..____. être créé qui ne peut pas être retourné à dateFormatQueue car il est plein. Cela ne générera pas d'erreur, cela entraînera simplement la pénalisation de certains SimpleDateFormat qui ne sont utilisés qu'une seule fois.

1
SamJ

Imaginez que votre application comporte un seul thread. Pourquoi voudriez-vous alors synchroniser l'accès à la variable SimpleDateFormat?

0
Vortex