¿Por qué SimpleDateFormat de Java no es seguro para subprocesos?

Indique con un ejemplo de código por qué SimpleDateFormat no es inseguro. ¿Cuál es el problema en esta clase? ¿El problema con la función de formato de SimpleDateFormat ? Por favor, proporcione un código que demuestre este error en la clase.

FastDateFormat es seguro para los hilos. ¿Por qué? ¿Cuál es la diferencia b / w SimpleDateFormat y FastDateFormat?

Por favor, explique con un código que demuestre este problema?

SimpleDateFormat almacena resultados intermedios en campos de instancia. Entonces, si una instancia es utilizada por dos hilos, pueden ensuciar los resultados de los demás.

Al DateFormat el código fuente, se revela que hay un campo de instancia de Calendar , que las operaciones DateFormat en DateFormat / SimpleDateFormat

Por ejemplo, el parse(..) llama a calendar.clear() inicialmente y luego a calendar.add(..) . Si otro hilo invoca parse(..) antes de completar la primera invocación, borrará el calendario, pero la otra invocación esperará que se complete con resultados intermedios del cálculo.

Una forma de reutilizar los formatos de fecha sin negociar la seguridad de los hilos es colocarlos en ThreadLocal , algunas bibliotecas lo hacen. Eso es si necesita usar el mismo formato varias veces dentro de un hilo. Pero en caso de que esté utilizando un contenedor de servlets (que tenga un grupo de subprocesos), recuerde limpiar el local de subprocesos después de terminar.

Para ser sincero, no entiendo por qué necesitan el campo de instancia, pero así son las cosas. También puede usar joda-time DateTimeFormat que es seguro para la ejecución de hilos.

SimpleDateFormat es una clase concreta para formatear y analizar las fechas de una manera sensible a la configuración regional.

Desde JavaDoc ,

Pero los formatos de fecha no están sincronizados . Se recomienda crear instancias de formato separadas para cada subproceso. Si varios subprocesos acceden a un formato al mismo tiempo, it must be synchronized externally .

Para hacer que la clase SimpleDateFormat sea segura para subprocesos, observe los siguientes enfoques :

  • Cree una nueva instancia de SimpleDateFormat cada vez que necesite usar una. Aunque esto es seguro para subprocesos, es el enfoque más lento posible.
  • Use sincronización Esta es una mala idea porque nunca debe estrangular sus hilos en un servidor.
  • Use un ThreadLocal. Este es el enfoque más rápido de los 3 (ver http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html ).

DateTimeFormatter en Java 8 es una alternativa inmutable y segura para SimpleDateFormat 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 versión 3.2 de commons-lang tendrá la clase FastDateParser que es un sustituto seguro de SimpleDateFormat de SimpleDateFormat para el calendario gregoriano. Vea LANG-909 para más información.

Aquí está el ejemplo que resulta en un extraño error. Incluso Google no da resultados:

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

Y resultado:

 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) 

Aquí hay un ejemplo de código que demuestra la falla en la clase. He comprobado: el problema ocurre cuando se usa el análisis sintáctico y también cuando solo se utiliza el formato.

Aquí hay un ejemplo que define un objeto SimpleDateFormat como un campo estático. Cuando dos o más subprocesos acceden a “someMethod” al mismo tiempo con diferentes fechas, pueden interferir con los resultados de los demás.

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

Puede crear un servicio como el siguiente y usar jmeter para simular usuarios concurrentes utilizando el mismo objeto SimpleDateFormat formateando fechas diferentes y sus resultados se perderán.

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

}

El código y la secuencia de comandos jmeter se pueden descargar aquí .

Si desea utilizar el mismo formato de fecha entre varios hilos, declare como estático y sincronícelo en la variable de instancia cuando lo use …

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