web-dev-qa-db-fra.com

Pourquoi SimpleDateFormat de Java n'est-il pas thread-safe?

S'il vous plaît dites avec un exemple de code pourquoi SimpleDateFormat n'est pas threadsafe. Quel est le problème dans cette classe? Le problème avec la fonction de format de SimpleDateFormat? S'il vous plaît donner un code qui démontre cette erreur en classe.

FastDateFormat est threadsafe. Pourquoi? Quelle est la différence entre les SimpleDateFormat et FastDateFormat?

S'il vous plaît expliquer avec un code qui illustre ce problème?

219
Vivek Sharma

SimpleDateFormat stocke les résultats intermédiaires dans les champs d'instance. Ainsi, si une instance est utilisée par deux threads, ils peuvent interférer avec les résultats de l'autre.

En regardant code source , il est indiqué qu'il existe un champ d'instance Calendar, utilisé par les opérations sur DateFormat/SimpleDateFormat.

Par exemple, parse(..) appelle calendar.clear() initialement, puis calendar.add(..). Si un autre thread appelle parse(..) avant la fin de la première invocation, le calendrier sera effacé, mais l'autre invocation s'attendra à ce qu'il soit rempli avec les résultats intermédiaires du calcul.

Une façon de réutiliser les formats de date sans échanger de sécurité sur les threads consiste à les placer dans un ThreadLocal - certaines bibliothèques le font. C'est si vous devez utiliser le même format plusieurs fois dans un fil. Mais si vous utilisez un conteneur de servlets (qui a un pool de threads), n'oubliez pas de nettoyer le thread local lorsque vous avez terminé.

Pour être honnête, je ne comprends pas pourquoi ils ont besoin du champ d'instance, mais c'est comme ça. Vous pouvez également utiliser joda-timeDateTimeFormat qui est threadsafe.

239
Bozho

SimpleDateFormat est une classe concrète permettant de formater et d’analyser les dates en tenant compte des paramètres régionaux.

De la JavaDoc ,

Mais les formats de date sont non 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, it must be synchronized externally.

Pour rendre la classe SimpleDateFormat thread-safe, regardez les approches suivantes :

  • Créez une nouvelle instance SimpleDateFormat chaque fois que vous en avez besoin. Bien que cela soit thread-safe, c'est l'approche la plus lente possible.
  • Utilisez la synchronisation. C'est une mauvaise idée, car vous ne devriez jamais induire en erreur vos threads sur un serveur.
  • Utilisez un ThreadLocal. C'est l'approche la plus rapide des 3 (voir http://www.javacodegeeks.com/2010/07/Java-best-practices-dateformat-in.html ).
59
sgokhales

DateTimeFormatter in Java 8 est une alternative immuable et thread-safe à SimpleDateFormat .

36
TheKojuEffect

ThreadLocal + SimpleDateFormat = SimpleDateFormatThreadSafe

package com.foocoders.text;

import Java.text.AttributedCharacterIterator;
import Java.text.DateFormatSymbols;
import Java.text.FieldPosition;
import Java.text.NumberFormat;
import Java.text.ParseException;
import Java.text.ParsePosition;
import Java.text.SimpleDateFormat;
import Java.util.Calendar;
import Java.util.Date;
import Java.util.Locale;
import Java.util.TimeZone;

public class SimpleDateFormatThreadSafe extends SimpleDateFormat {

    private static final long serialVersionUID = 5448371898056188202L;
    ThreadLocal<SimpleDateFormat> localSimpleDateFormat;

    public SimpleDateFormatThreadSafe() {
        super();
        localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat();
            }
        };
    }

    public SimpleDateFormatThreadSafe(final String pattern) {
        super(pattern);
        localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat(pattern);
            }
        };
    }

    public SimpleDateFormatThreadSafe(final String pattern, final DateFormatSymbols formatSymbols) {
        super(pattern, formatSymbols);
        localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat(pattern, formatSymbols);
            }
        };
    }

    public SimpleDateFormatThreadSafe(final String pattern, final Locale locale) {
        super(pattern, locale);
        localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat(pattern, locale);
            }
        };
    }

    public Object parseObject(String source) throws ParseException {
        return localSimpleDateFormat.get().parseObject(source);
    }

    public String toString() {
        return localSimpleDateFormat.get().toString();
    }

    public Date parse(String source) throws ParseException {
        return localSimpleDateFormat.get().parse(source);
    }

    public Object parseObject(String source, ParsePosition pos) {
        return localSimpleDateFormat.get().parseObject(source, pos);
    }

    public void setCalendar(Calendar newCalendar) {
        localSimpleDateFormat.get().setCalendar(newCalendar);
    }

    public Calendar getCalendar() {
        return localSimpleDateFormat.get().getCalendar();
    }

    public void setNumberFormat(NumberFormat newNumberFormat) {
        localSimpleDateFormat.get().setNumberFormat(newNumberFormat);
    }

    public NumberFormat getNumberFormat() {
        return localSimpleDateFormat.get().getNumberFormat();
    }

    public void setTimeZone(TimeZone zone) {
        localSimpleDateFormat.get().setTimeZone(zone);
    }

    public TimeZone getTimeZone() {
        return localSimpleDateFormat.get().getTimeZone();
    }

    public void setLenient(boolean lenient) {
        localSimpleDateFormat.get().setLenient(lenient);
    }

    public boolean isLenient() {
        return localSimpleDateFormat.get().isLenient();
    }

    public void set2DigitYearStart(Date startDate) {
        localSimpleDateFormat.get().set2DigitYearStart(startDate);
    }

    public Date get2DigitYearStart() {
        return localSimpleDateFormat.get().get2DigitYearStart();
    }

    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
        return localSimpleDateFormat.get().format(date, toAppendTo, pos);
    }

    public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
        return localSimpleDateFormat.get().formatToCharacterIterator(obj);
    }

    public Date parse(String text, ParsePosition pos) {
        return localSimpleDateFormat.get().parse(text, pos);
    }

    public String toPattern() {
        return localSimpleDateFormat.get().toPattern();
    }

    public String toLocalizedPattern() {
        return localSimpleDateFormat.get().toLocalizedPattern();
    }

    public void applyPattern(String pattern) {
        localSimpleDateFormat.get().applyPattern(pattern);
    }

    public void applyLocalizedPattern(String pattern) {
        localSimpleDateFormat.get().applyLocalizedPattern(pattern);
    }

    public DateFormatSymbols getDateFormatSymbols() {
        return localSimpleDateFormat.get().getDateFormatSymbols();
    }

    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
        localSimpleDateFormat.get().setDateFormatSymbols(newFormatSymbols);
    }

    public Object clone() {
        return localSimpleDateFormat.get().clone();
    }

    public int hashCode() {
        return localSimpleDateFormat.get().hashCode();
    }

    public boolean equals(Object obj) {
        return localSimpleDateFormat.get().equals(obj);
    }

}

https://Gist.github.com/pablomoretti/97482

33
Pablo Moretti

La version 3.2 de _commons-lang_ aura la classe FastDateParser qui est un substitut sans risque de thread de SimpleDateFormat au calendrier grégorien. Voir LANG-909 pour plus d'informations.

15
dma_k

Voici l'exemple qui provoque une erreur étrange. Même Google ne donne aucun résultat:

public class ExampleClass {

private static final Pattern dateCreateP = Pattern.compile("Дата подачи:\\s*(.+)");
private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy");

public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(100);
    while (true) {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                workConcurrently();
            }
        });
    }
}

public static void workConcurrently() {
    Matcher matcher = dateCreateP.matcher("Дата подачи: 19:30:55 03.05.2015");
    Timestamp startAdvDate = null;
    try {
        if (matcher.find()) {
            String dateCreate = matcher.group(1);
            startAdvDate = new Timestamp(sdf.parse(dateCreate).getTime());
        }
    } catch (Throwable th) {
        th.printStackTrace();
    }
    System.out.print("OK ");
}
}

Et résultat:

OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK Java.lang.NumberFormatException: For input string: ".201519E.2015192E2"
at Sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.Java:2043)
at Sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.Java:110)
at Java.lang.Double.parseDouble(Double.Java:538)
at Java.text.DigitList.getDouble(DigitList.Java:169)
at Java.text.DecimalFormat.parse(DecimalFormat.Java:2056)
at Java.text.SimpleDateFormat.subParse(SimpleDateFormat.Java:1869)
at Java.text.SimpleDateFormat.parse(SimpleDateFormat.Java:1514)
at Java.text.DateFormat.parse(DateFormat.Java:364)
at com.nonscalper.webscraper.processor.av.ExampleClass.workConcurrently(ExampleClass.Java:37)
at com.nonscalper.webscraper.processor.av.ExampleClass$1.run(ExampleClass.Java:25)
at Java.util.concurrent.Executors$RunnableAdapter.call(Executors.Java:511)
at Java.util.concurrent.FutureTask.run(FutureTask.Java:266)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1142)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:617)
at Java.lang.Thread.run(Thread.Java:745)
10
user2602807

Voici un exemple de code qui prouve la faute dans la classe. J'ai vérifié: le problème se produit lors de l'utilisation de l'analyse et également lorsque vous utilisez uniquement le format.

4
Hans-Peter Störr

Voici un exemple qui définit un objet SimpleDateFormat en tant que champ statique. Lorsque deux ou plusieurs threads accèdent à "someMethod" simultanément avec des dates différentes, ils peuvent interférer avec les résultats de l’autre.

    public class SimpleDateFormatExample {
         private static final SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

         public String someMethod(Date date) {
            return simpleFormat.format(date);
         }
    }

Vous pouvez créer un service comme ci-dessous et utiliser jmeter pour simuler des utilisateurs simultanés à l'aide du même objet SimpleDateFormat mettant en forme différentes dates et leurs résultats seront perturbés.

public class FormattedTimeHandler extends AbstractHandler {

private static final String OUTPUT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
private static final String INPUT_TIME_FORMAT = "yyyy-MM-ddHH:mm:ss";
private static final SimpleDateFormat simpleFormat = new SimpleDateFormat(OUTPUT_TIME_FORMAT);
// Apache commons lang3 FastDateFormat is threadsafe
private static final FastDateFormat fastFormat = FastDateFormat.getInstance(OUTPUT_TIME_FORMAT);

public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {

    response.setContentType("text/html;charset=utf-8");
    response.setStatus(HttpServletResponse.SC_OK);
    baseRequest.setHandled(true);

    final String inputTime = request.getParameter("time");
    Date date = LocalDateTime.parse(inputTime, DateTimeFormat.forPattern(INPUT_TIME_FORMAT)).toDate();

    final String method = request.getParameter("method");
    if ("SimpleDateFormat".equalsIgnoreCase(method)) {
        // use SimpleDateFormat as a static constant field, not thread safe
        response.getWriter().println(simpleFormat.format(date));
    } else if ("FastDateFormat".equalsIgnoreCase(method)) {
        // use Apache commons lang3 FastDateFormat, thread safe
        response.getWriter().println(fastFormat.format(date));
    } else {
        // create new SimpleDateFormat instance when formatting date, thread safe
        response.getWriter().println(new SimpleDateFormat(OUTPUT_TIME_FORMAT).format(date));
    }
}

public static void main(String[] args) throws Exception {
    // embedded jetty configuration, running on port 8090. change it as needed.
    Server server = new Server(8090);
    server.setHandler(new FormattedTimeHandler());

    server.start();
    server.join();
}

}

Le code et le script jmeter peuvent être téléchargés ici .

3
ylu