Perché SimpleDateFormat di Java non è thread-safe?

Si prega di dire con un esempio di codice perché SimpleDateFormat non è protetto da thread. Qual è il problema in questa class? Il problema con la funzione di formattazione di SimpleDateFormat è ? Si prega di fornire un codice che dimostri questo errore in class.

FastDateFormat è thread-safe. Perché? qual è la differenza tra il SimpleDateFormat e FastDateFormat?

Si prega di spiegare con un codice che dimostra questo problema?

SimpleDateFormat memorizza risultati intermedi nei campi di istanza. Quindi, se un’istanza viene utilizzata da due thread, possono compromettere i risultati l’uno dell’altro.

L’esame del codice sorgente rivela che esiste un campo di istanza di Calendar , che viene utilizzato dalle operazioni su DateFormat / SimpleDateFormat

Ad esempio, parse(..) chiama inizialmente calendar.clear() e quindi calendar.add(..) . Se un altro thread richiama l’ parse(..) prima del completamento della prima chiamata, cancella il calendario, ma l’altra chiamata si aspetta che venga popolata con risultati intermedi del calcolo.

Un modo per riutilizzare i formati di data senza la negoziazione di thread-safety è metterli in un ThreadLocal – alcune librerie lo fanno. Questo è se hai bisogno di utilizzare lo stesso formato più volte all’interno di un thread. Ma nel caso si stia utilizzando un container servlet (che ha un pool di thread), ricordarsi di pulire il thread-local dopo aver finito.

Ad essere onesti, non capisco perché abbiano bisogno del campo istanza, ma è così. Puoi anche usare DateTimeFormat joda-time che è protetto da thread.

SimpleDateFormat è una class concreta per la formattazione e l’analisi delle date in un modo sensibile alle SimpleDateFormat internazionali.

Da JavaDoc ,

Ma i formati Data non sono sincronizzati . Si consiglia di creare istanze di formato separate per ogni thread. Se più thread accedono a un formato contemporaneamente, it must be synchronized externally .

Per rendere la class SimpleDateFormat sicura per il thread, osservare i seguenti approcci :

  • Creare una nuova istanza SimpleDateFormat ogni volta che è necessario utilizzarne una. Anche se questo è thread-safe, è l’approccio più lento ansible.
  • Usa la sincronizzazione. Questa è una ctriggers idea perché non dovresti mai soffocare i tuoi thread su un server.
  • Usa un ThreadLocal. Questo è l’approccio più veloce del 3 (vedi http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html ).

DateTimeFormatter in Java 8 è un’alternativa immutabile e thread-safe a SimpleDateFormat .

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 localSimpleDateFormat; public SimpleDateFormatThreadSafe() { super(); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(); } }; } public SimpleDateFormatThreadSafe(final String pattern) { super(pattern); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern); } }; } public SimpleDateFormatThreadSafe(final String pattern, final DateFormatSymbols formatSymbols) { super(pattern, formatSymbols); localSimpleDateFormat = new ThreadLocal() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern, formatSymbols); } }; } public SimpleDateFormatThreadSafe(final String pattern, final Locale locale) { super(pattern, locale); localSimpleDateFormat = new ThreadLocal() { 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

La versione 3.2 di commons-lang avrà class FastDateParser che è un sostituto sicuro per thread di SimpleDateFormat per il calendario Gregoriano. Vedi LANG-909 per maggiori informazioni.

Ecco l’esempio che provoca uno strano errore. Anche Google non dà risultati:

 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 "); } } 

E risultato:

 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) 

Ecco un esempio di codice che dimostra l’errore nella class. Ho controllato: il problema si verifica quando si utilizza parse e anche quando si utilizza solo il formato.

Ecco un esempio definisce un object SimpleDateFormat come un campo statico. Quando due o più thread accedono “someMethod” in concomitanza con date diverse, possono interferire con i risultati dell’altro.

  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); } } 

È ansible creare un servizio come di seguito e utilizzare jmeter per simulare utenti concorrenti che utilizzano lo stesso object SimpleDateFormat formattando date diverse e i loro risultati verranno incasinati.

 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(); } 

}

Il codice e lo script jmeter possono essere scaricati qui .

Se si desidera utilizzare lo stesso formato di data tra più thread, dichiararlo come statico e sincronizzarlo sulla variabile di istanza quando lo si utilizza …

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