Pourquoi le SimpleDateFormat de Java n'est-il pas thread-safe? [dupliquer]

cette question a déjà une réponse ici:

s'il vous plaît dire avec un exemple de code pourquoi est SimpleDateFormat pas threadsafe. Quel est le problème dans cette classe? est le problème avec le format fonction de SimpleDateFormat ? Veuillez donner un code qui démontre ce défaut en classe.

FastDateFormat est thread-safe. Pourquoi? Quelle est la différence entre le SimpleDateFormat et le FastDateFormat?

Veuillez expliquer avec un code qui démontre cette question?

197
demandé sur Flow 2011-07-27 11:24:26

9 réponses

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

en regardant le code source révèle qu'il y a un champ d'instance Calendar , qui est utilisé par les opérations sur DateFormat / SimpleDateFormat 1519110920"

par exemple parse(..) appelle calendar.clear() d'abord puis calendar.add(..) . Si un autre fil invoque parse(..) avant l'achèvement de la première invocation, il effacera le calendrier, 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 fil trading-la sécurité est de les mettre dans un ThreadLocal - certaines bibliothèques font cela. C'est si vous avez besoin d'utiliser le même format plusieurs fois dans un seul thread. Mais dans le cas où vous utilisez un conteneur de servlet (qui a un pool de thread), rappelez-vous de nettoyer le fil-local après avoir 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-time DateTimeFormat qui est threadsafe.

223
répondu Bozho 2011-07-27 07:34:34

SimpleDateFormat est une classe de béton pour le formatage et l'analyse des dates d'une manière sensible à la localisation.

de la JavaDoc ,

mais les formats de Date sont non synchronisé . Il est recommandé de créer séparer les instances de format pour chaque thread. Si plusieurs threads d'accès un format simultané, it must be synchronized externally .

pour rendre le SimpleDateFormat classe thread-safe, regardez le approches suivantes :

  • créez une nouvelle instance SimpleDateFormat chaque fois que vous avez besoin d'en utiliser une. Bien qu'il s'agisse d'une approche sans fil, c'est l'approche la plus lente possible.
  • utiliser la synchronisation. C'est une mauvaise idée parce que vous ne devriez jamais étouffer-pointer vos threads sur un serveur.
  • Use un ThreadLocal. C'est l'approche la plus rapide des 3 (voir http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html ).
56
répondu Javascript is GOD 2011-07-27 07:32:36

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

31
répondu TheKojuEffect 2016-03-05 18:54:05

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/9748230

30
répondu Pablo Moretti 2015-04-12 03:21:37

Version 3.2 de commons-lang aura la classe FastDateParser qui est un substitut sans fil de SimpleDateFormat pour le calendrier grégorien. Pour en savoir plus, lisez LANG-909 .

13
répondu dma_k 2013-08-08 09:08:39

Voici l'exemple qui résulte en 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.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)
9
répondu user2602807 2016-11-03 15:14:45

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 parse et aussi lorsque vous utilisez seulement le format.

4
répondu Hans-Peter Störr 2013-02-06 17:58:15

voici un exemple qui définit un objet SimpleDateFormat comme un champ statique. Lorsque deux ou plusieurs threads accèdent à "someMethod" simultanément avec des dates différentes, ils peuvent brouiller 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 les utilisateurs concurrents en utilisant le même formatage objet SimpleDateFormat différentes dates et leurs résultats seront foiré.

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 .

1
répondu ylu 2018-09-09 21:24:43

si vous voulez utiliser le même format de date parmi plusieurs threads, déclarez-le comme statique et synchronisez sur la variable d'instance en l'utilisant...

static private SimpleDateFormat sdf = new SimpleDateFormat("....");

synchronized(sdf)
{
   // use the instance here to format a date
}


// The above makes it thread safe
-3
répondu Rodney P. Barbati 2016-09-04 17:49:17