tipo texto pasar numero formato fecha dar convertir como cadena java multithreading date-format

texto - "Java DateFormat no es inseguro" ¿a qué lleva esto?



de texto a fecha en java (10)

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, se debe sincronizar externamente.

Esto significa que usted tiene un objeto de DateFormat y está accediendo al mismo objeto desde dos hilos diferentes y está llamando al método de formateo sobre ese objeto. Ambos hilos ingresarán en el mismo método al mismo tiempo en el mismo objeto para que pueda visualizarlo ganado no dar lugar a un resultado adecuado

Si tiene que trabajar con DateFormat de alguna forma, entonces debe hacer algo

public synchronized myFormat(){ // call here actual format method }

Todo el mundo advierte que Java DateFormat no es seguro para hilos y entiendo el concepto teóricamente.

Pero no puedo visualizar los problemas reales que podemos enfrentar debido a esto. Digamos, tengo un campo DateFormat en una clase y lo mismo se usa en diferentes métodos en la clase (fechas de formateo) en un entorno de subprocesos múltiples.

Esto causará:

  • cualquier excepción como excepción de formato
  • discrepancia en los datos
  • cualquier otro problema?

Además, explica por qué.


A grandes rasgos, no debería definir un DateFormat como variable de instancia de un objeto al que acceden muchos hilos o static .

Los formatos de fecha no están sincronizados. Se recomienda crear instancias de formato separadas para cada subproceso.

Entonces, en caso de que se Foo.handleBar(..) su Foo.handleBar(..) por varios hilos, en lugar de:

public class Foo { private DateFormat df = new SimpleDateFormat("dd/mm/yyyy"); public void handleBar(Bar bar) { bar.setFormattedDate(df.format(bar.getStringDate()); } }

Deberías usar:

public class Foo { public void handleBar(Bar bar) { DateFormat df = new SimpleDateFormat("dd/mm/yyyy"); bar.setFormattedDate(df.format(bar.getStringDate()); } }

Además, en todos los casos, no tiene un DateFormat static

Como señaló Jon Skeet, puede tener variables de instancia estáticas y compartidas en caso de que realice una sincronización externa (es decir, utilice synchronized en llamadas al DateFormat )


En la mejor respuesta, dogbane dio un ejemplo del uso de la función de parse y a qué conduce. A continuación se muestra un código que le permite verificar la función de format .

Tenga en cuenta que si cambia el número de ejecutores (subprocesos concurrentes) obtendrá diferentes resultados. De mis experimentos:

  • Deje newFixedThreadPool establecido en 5 y el ciclo fallará cada vez.
  • Se establece en 1 y el ciclo siempre funcionará (obviamente, ya que todas las tareas se ejecutan una por una)
  • Establezca en 2 y el bucle tiene solo alrededor del 6% de posibilidades de funcionar.

Supongo que YMMV depende de tu procesador.

La función de formateo falla al formatear el tiempo desde un hilo diferente. Esto se debe a que la función de format interno está utilizando un objeto de calendar que está configurado al inicio de la función de format . Y el objeto de calendar es una propiedad de la clase SimpleDateFormat . Suspiro...

/** * Test SimpleDateFormat.format (non) thread-safety. * * @throws Exception */ private static void testFormatterSafety() throws Exception { final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final Calendar calendar1 = new GregorianCalendar(2013,1,28,13,24,56); final Calendar calendar2 = new GregorianCalendar(2014,1,28,13,24,56); String expected[] = {"2013-02-28 13:24:56", "2014-02-28 13:24:56"}; Callable<String> task1 = new Callable<String>() { @Override public String call() throws Exception { return "0#" + format.format(calendar1.getTime()); } }; Callable<String> task2 = new Callable<String>() { @Override public String call() throws Exception { return "1#" + format.format(calendar2.getTime()); } }; //pool with X threads // note that using more then CPU-threads will not give you a performance boost ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<String>> results = new ArrayList<>(); //perform some date conversions for (int i = 0; i < 1000; i++) { results.add(exec.submit(task1)); results.add(exec.submit(task2)); } exec.shutdown(); //look at the results for (Future<String> result : results) { String answer = result.get(); String[] split = answer.split("#"); Integer calendarNo = Integer.parseInt(split[0]); String formatted = split[1]; if (!expected[calendarNo].equals(formatted)) { System.out.println("formatted: " + formatted); System.out.println("expected: " + expected[calendarNo]); System.out.println("answer: " + answer); throw new Exception("formatted != expected"); /** } else { System.out.println("OK answer: " + answer); /**/ } } System.out.println("OK: Loop finished"); }


Esperaría daños en los datos, por ejemplo, si está analizando dos fechas al mismo tiempo, podría tener una llamada contaminada por datos de otra.

Es fácil imaginar cómo podría suceder esto: el análisis a menudo implica mantener una cierta cantidad de estado en cuanto a lo que has leído hasta ahora. Si dos hilos están pisoteando en el mismo estado, tendrás problemas. Por ejemplo, DateFormat expone un campo de calendar de tipo Calendar , y mirando el código de SimpleDateFormat , algunos métodos llaman calendar.set(...) y otros llaman calendar.get(...) . Esto claramente no es seguro para subprocesos.

No he DateFormat detalles exactos de por qué DateFormat no es seguro para subprocesos, pero para mí es suficiente saber que no es seguro sin sincronización: los modales de no seguridad podrían incluso cambiar entre lanzamientos.

Personalmente, usaría los analizadores de Joda Time , ya que son seguros para subprocesos, y Joda Time es una API de fecha y hora mucho mejor para empezar :)


Este es mi código simple que muestra que DateFormat no es seguro para subprocesos.

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class DateTimeChecker { static DateFormat df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); public static void main(String args[]){ String target1 = "Thu Sep 28 20:29:30 JST 2000"; String target2 = "Thu Sep 28 20:29:30 JST 2001"; String target3 = "Thu Sep 28 20:29:30 JST 2002"; runThread(target1); runThread(target2); runThread(target3); } public static void runThread(String target){ Runnable myRunnable = new Runnable(){ public void run(){ Date result = null; try { result = df.parse(target); } catch (ParseException e) { e.printStackTrace(); System.out.println("Ecxfrt"); } System.out.println(Thread.currentThread().getName() + " " + result); } }; Thread thread = new Thread(myRunnable); thread.start(); } }

Como todos los subprocesos usan el mismo objeto SimpleDateFormat, arroja la siguiente excepción.

Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source) java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source) java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source)

Pero si pasamos diferentes objetos a diferentes hilos, el código se ejecuta sin errores.

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class DateTimeChecker { static DateFormat df; public static void main(String args[]){ String target1 = "Thu Sep 28 20:29:30 JST 2000"; String target2 = "Thu Sep 28 20:29:30 JST 2001"; String target3 = "Thu Sep 28 20:29:30 JST 2002"; df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target1, df); df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target2, df); df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target3, df); } public static void runThread(String target, DateFormat df){ Runnable myRunnable = new Runnable(){ public void run(){ Date result = null; try { result = df.parse(target); } catch (ParseException e) { e.printStackTrace(); System.out.println("Ecxfrt"); } System.out.println(Thread.currentThread().getName() + " " + result); } }; Thread thread = new Thread(myRunnable); thread.start(); } }

Estos son los resultados.

Thread-0 Thu Sep 28 17:29:30 IST 2000 Thread-2 Sat Sep 28 17:29:30 IST 2002 Thread-1 Fri Sep 28 17:29:30 IST 2001


Las especificaciones de Format, NumberFormat, DateFormat, MessageFormat, etc. no fueron diseñadas para ser seguras para subprocesos. Además, el método de análisis llama al método Calendar.clone() y afecta las huellas de calendario, por lo que muchos subprocesos que analizan simultáneamente cambiarán la clonación de la instancia de Calendar.

Para obtener más información, estos son informes de errores como this y this , con los resultados del problema de seguridad de subprocesos DateFormat.


Los datos están corruptos. Ayer lo noté en mi programa multiproceso donde tenía DateFormat objeto DateFormat estático y llamé a su format() para los valores leídos a través de JDBC. Tenía SQL select statement donde leí la misma fecha con diferentes nombres ( SELECT date_from, date_from AS date_from1 ... ). Tales declaraciones estaban usando en 5 hilos para varias fechas en WHERE clasue. Las fechas parecían "normales", pero diferían en su valor, mientras que todas las fechas eran del mismo año, solo el mes y el día cambiaban.

Otras respuestas muestran la forma de evitar dicha corrupción. Hice mi DateFormat no estático, ahora es un miembro de una clase que llama a las declaraciones SQL. Probé también la versión estática con sincronización. Ambos funcionaron bien sin diferencias en el rendimiento.


Probémoslo.

Aquí hay un programa en el que varios subprocesos usan un SimpleDateFormat compartido.

Programa :

public static void main(String[] args) throws Exception { final DateFormat format = new SimpleDateFormat("yyyyMMdd"); Callable<Date> task = new Callable<Date>(){ public Date call() throws Exception { return format.parse("20101022"); } }; //pool with 5 threads ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<Date>> results = new ArrayList<Future<Date>>(); //perform 10 date conversions for(int i = 0 ; i < 10 ; i++){ results.add(exec.submit(task)); } exec.shutdown(); //look at the results for(Future<Date> result : results){ System.out.println(result.get()); } }

Ejecuta esto unas cuantas veces y verás:

Excepciones :

Aquí están algunos ejemplos:

1.

Caused by: java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48) at java.lang.Long.parseLong(Long.java:431) at java.lang.Long.parseLong(Long.java:468) at java.text.DigitList.getLong(DigitList.java:177) at java.text.DecimalFormat.parse(DecimalFormat.java:1298) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)

2.

Caused by: java.lang.NumberFormatException: For input string: ".10201E.102014E4" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224) at java.lang.Double.parseDouble(Double.java:510) at java.text.DigitList.getDouble(DigitList.java:151) at java.text.DecimalFormat.parse(DecimalFormat.java:1303) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)

3.

Caused by: java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084) at java.lang.Double.parseDouble(Double.java:510) at java.text.DigitList.getDouble(DigitList.java:151) at java.text.DecimalFormat.parse(DecimalFormat.java:1303) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1936) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312)

Resultados incorrectos :

Sat Oct 22 00:00:00 BST 2011 Thu Jan 22 00:00:00 GMT 1970 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Thu Oct 22 00:00:00 GMT 1970 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010

Resultados correctos :

Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010

Otro enfoque para usar DateFormats de forma segura en un entorno de subprocesos múltiples es utilizar una variable ThreadLocal para contener el objeto DateFormat , lo que significa que cada subproceso tendrá su propia copia y no necesita esperar a que otros subprocesos lo liberen. Así es como:

public class DateFormatTest { private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd"); } }; public Date convert(String source) throws ParseException{ Date d = df.get().parse(source); return d; } }

Aquí hay una buena post con más detalles.


Si está utilizando Java 8, puede usar DateTimeFormatter .

Un formateador creado a partir de un patrón se puede utilizar tantas veces como sea necesario, es inmutable y es seguro para subprocesos.

Código:

LocalDate date = LocalDate.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); String text = date.format(formatter); System.out.println(text);

Salida:

2017-04-17


Si hay varios subprocesos que manipulan / acceden a una única instancia de DateFormat y no se utiliza la sincronización, es posible obtener resultados codificados. Esto se debe a que varias operaciones no atómicas podrían estar cambiando el estado o viendo la memoria de manera incoherente.