safe - ¿Por qué el SimpleDateFormat de Java no es seguro para subprocesos?
simpledateformat java ejemplo (9)
Esta pregunta ya tiene una respuesta aquí:
Indique con un ejemplo de código por qué SimpleDateFormat no es threadsafe. ¿Cuál es el problema en esta clase? ¿El problema con la función de formato de SimpleDateFormat ? Por favor, indique un código que demuestre este error en clase.
FastDateFormat es seguro para subprocesos. ¿Por qué? ¿Cuál es la diferencia entre el SimpleDateFormat y el FastDateFormat?
Por favor explique con un código que demuestre este problema?
Aquí está el ejemplo que resulta en un error extraño. 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 el 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 prueba la falla en la clase. He comprobado: el problema se produce cuando se usa Parse y también cuando solo se usa el formato.
Aquí hay un ejemplo que define un objeto SimpleDateFormat como un campo estático. Cuando dos o más subprocesos acceden a "algún Método" simultáneamente con fechas diferentes, pueden alterar los resultados de cada uno.
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 que se muestra a continuación y usar jmeter para simular usuarios concurrentes utilizando el mismo objeto SimpleDateFormat que formatea fechas diferentes y sus resultados se desordenará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 el script jmeter se pueden descargar here .
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. Ver LANG-909
para más información.
Si desea utilizar el mismo formato de fecha entre varios subprocesos, declare como estático y sincronice 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
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);
}
}
SimpleDateFormat
es una clase concreta para formatear y analizar fechas de una manera sensible al entorno local.
Desde el SimpleDateFormat ,
Pero los formatos de fecha no están sincronizados . Se recomienda crear instancias de formato separadas para cada hilo. Si varios subprocesos acceden a un formato simultáneamente,
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.
- Usa la sincronización. Esta es una mala idea, ya que nunca debe bloquear sus hilos en un servidor.
- Utilice un ThreadLocal. Este es el enfoque más rápido de los 3 (consulte 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
.
SimpleDateFormat
almacena resultados intermedios en campos de instancia. Entonces, si una instancia es utilizada por dos hilos, pueden ensuciar los resultados del otro.
Mirar el código fuente revela que hay un campo de instancia de Calendar
, que es utilizado por las operaciones en DateFormat
/ SimpleDateFormat
Por ejemplo, parse(..)
llama a calendar.clear()
inicialmente y luego calendar.add(..)
. Si otro hilo invoca a parse(..)
antes de completar la primera invocación, borrará el calendario, pero la otra invocación esperará que se complete con los resultados intermedios del cálculo.
Una forma de reutilizar los formatos de fecha sin intercambiar seguridad de subprocesos es colocarlos en un 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 tiene un grupo de subprocesos), recuerde limpiar el subproceso local después de terminar.
Para ser honesto, no entiendo por qué necesitan el campo de instancia, pero así es como es. También puede usar joda-time DateTimeFormat
que es threadsafe.