Pourquoi le SimpleDateFormat de Java n’est-il pas compatible avec les threads?

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? Est-ce que le problème avec la fonction de format de SimpleDateFormat ? Veuillez donner un code qui démontre cette erreur en classe.

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

S’il vous plaît expliquer avec un code qui démontre ce problème?

SimpleDateFormat stocke les résultats intermédiaires dans les champs d’instance. Donc, si une instance est utilisée par deux threads, ils peuvent se gâter mutuellement.

L’examen du code source révèle qu’il existe un champ d’instance Calendar , utilisé par les opérations sur DateFormat / SimpleDateFormat

Par exemple, parse(..) appelle initialement calendar.clear() , puis calendar.add(..) . Si un autre thread appelle l’ parse(..) avant la fin de la première invocation, le calendrier sera effacé, mais l’autre appel attendra 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 thread-safety est de les placer dans un ThreadLocal – certaines bibliothèques le font. C’est si vous devez utiliser le même format plusieurs fois dans un thread. Mais si vous utilisez un conteneur de servlet (qui possède un pool de threads), n’oubliez pas de nettoyer le thread-local une fois que vous avez terminé.

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

SimpleDateFormat est une classe concrète pour le formatage et l’parsing des dates d’une manière sensible aux parameters régionaux.

À partir de JavaDoc ,

Mais les formats de date ne sont pas synchronisés . Il est recommandé de créer des instances de format séparées pour chaque thread. Si plusieurs threads accèdent simultanément à un format, it must be synchronized externally .

Pour rendre la classe SimpleDateFormat adaptée aux threads, examinez les approches suivantes :

  • Créez une nouvelle instance SimpleDateFormat chaque fois que vous en avez besoin. Bien que ce soit thread-safe, c’est l’approche la plus lente possible.
  • Utiliser la synchronisation C’est une mauvaise idée car vous ne devez jamais étouffer vos threads sur un serveur.
  • Utilisez un ThreadLocal. C’est l’approche la plus rapide du 3 (voir http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html ).

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

ThreadLocal + SimpleDateFormat = SimpleDateFormatThreadSafe

 package com.foocoders.text; import java.text.AtsortingbutedCharacterIterator; 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 localSimpleDateFormat; public SimpleDateFormatThreadSafe() { super(); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(); } }; } public SimpleDateFormatThreadSafe(final Ssortingng pattern) { super(pattern); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern); } }; } public SimpleDateFormatThreadSafe(final Ssortingng pattern, final DateFormatSymbols formatSymbols) { super(pattern, formatSymbols); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern, formatSymbols); } }; } public SimpleDateFormatThreadSafe(final Ssortingng pattern, final Locale locale) { super(pattern, locale); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern, locale); } }; } public Object parseObject(Ssortingng source) throws ParseException { return localSimpleDateFormat.get().parseObject(source); } public Ssortingng toSsortingng() { return localSimpleDateFormat.get().toSsortingng(); } public Date parse(Ssortingng source) throws ParseException { return localSimpleDateFormat.get().parse(source); } public Object parseObject(Ssortingng 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 SsortingngBuffer format(Date date, SsortingngBuffer toAppendTo, FieldPosition pos) { return localSimpleDateFormat.get().format(date, toAppendTo, pos); } public AtsortingbutedCharacterIterator formatToCharacterIterator(Object obj) { return localSimpleDateFormat.get().formatToCharacterIterator(obj); } public Date parse(Ssortingng text, ParsePosition pos) { return localSimpleDateFormat.get().parse(text, pos); } public Ssortingng toPattern() { return localSimpleDateFormat.get().toPattern(); } public Ssortingng toLocalizedPattern() { return localSimpleDateFormat.get().toLocalizedPattern(); } public void applyPattern(Ssortingng pattern) { localSimpleDateFormat.get().applyPattern(pattern); } public void applyLocalizedPattern(Ssortingng 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

La version 3.2 de commons-lang aura la classe FastDateParser qui sera un substitut de thread FastDateParser de SimpleDateFormat pour le calendrier grégorien. Voir LANG-909 pour plus d’informations.

Voici l’exemple qui entraîne une erreur étrange. Même Google ne donne aucun résultat:

 public class ExampleClass { private static final Pattern dateCreateP = Pattern.comstack("Дата подачи:\\s*(.+)"); private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy"); public static void main(Ssortingng[] 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()) { Ssortingng 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 ssortingng: ".201519E.2015192E2" at sun.misc.FloatingDecimal.readJavaFormatSsortingng(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) 

Voici un exemple de code qui prouve la faute dans la classe. J’ai vérifié: le problème se produit lorsque vous utilisez l’parsing et que vous utilisez uniquement le format.

Voici un exemple qui définit un object SimpleDateFormat en tant que champ statique. Lorsque deux threads ou plus accèdent à «someMethod» en même temps que des dates différentes, ils peuvent interférer avec les résultats de chacun.

  public class SimpleDateFormatExample { private static final SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); public Ssortingng 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 en utilisant le même object SimpleDateFormat en formatant différentes dates et leurs résultats seront faussés.

 public class FormattedTimeHandler extends AbstractHandler { private static final Ssortingng OUTPUT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; private static final Ssortingng 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(Ssortingng 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 Ssortingng inputTime = request.getParameter("time"); Date date = LocalDateTime.parse(inputTime, DateTimeFormat.forPattern(INPUT_TIME_FORMAT)).toDate(); final Ssortingng 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(Ssortingng[] 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 .

Si vous souhaitez utiliser le même format de date entre plusieurs threads, déclarez-le comme statique et synchronisez-le sur la variable d’instance lorsque vous l’utilisez …

 static private SimpleDateFormat sdf = new SimpleDateFormat("...."); synchronized(sdf) { // use the instance here to format a date } // The above makes it thread safe