Sincronizar el acceso a SimpleDateFormat

90

El javadoc para SimpleDateFormat indica que SimpleDateFormat no está sincronizado.

"Los formatos de fecha no están sincronizados. Se recomienda crear instancias de formato independientes para cada subproceso. Si varios subprocesos acceden a un formato al mismo tiempo, debe sincronizarse externamente".

Pero, ¿cuál es el mejor enfoque para usar una instancia de SimpleDateFormat en un entorno de subprocesos múltiples? Aquí hay algunas opciones en las que he pensado, he usado las opciones 1 y 2 en el pasado, pero tengo curiosidad por saber si hay mejores alternativas o cuál de estas opciones ofrecería el mejor rendimiento y concurrencia.

Opción 1: crear instancias locales cuando sea necesario

public String formatDate(Date d) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(d);
}

Opción 2: Cree una instancia de SimpleDateFormat como una variable de clase pero sincronice el acceso a ella.

private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String formatDate(Date d) {
    synchronized(sdf) {
        return sdf.format(d);
    }
}

Opción 3: cree un ThreadLocal para almacenar una instancia diferente de SimpleDateFormat para cada hilo.

private ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public String formatDate(Date d) {
    SimpleDateFormat sdf = tl.get();
    if(sdf == null) {
        sdf = new SimpleDateFormat("yyyy-MM-hh");
        tl.set(sdf);
    }
    return sdf.format(d);
}
3urdoch
fuente
10
+1 por plantear este problema. Mucha gente piensa que SimpleDateFormat es seguro para subprocesos (veo suposiciones en todas partes).
Adam Gent
Para obtener más información sobre el enfoque ThreadLocal, consulte: javaspecialists.eu/archive/Issue172.html
miner49r
Y para saber por qué, consulte esta pregunta: stackoverflow.com/questions/6840803/…
Raedwald
@ 3urdoch ¿Omitió por error la palabra clave 'estática' en la Opción-2?
M Faisal Hameed

Respuestas:

43
  1. Crear SimpleDateFormat es caro . No use esto a menos que se haga con poca frecuencia.

  2. Está bien si puedes vivir con un poco de bloqueo. Úselo si formatDate () no se usa mucho.

  3. La opción más rápida SI reutiliza subprocesos ( grupo de subprocesos ). Utiliza más memoria que 2. y tiene una sobrecarga de inicio más alta.

Para aplicaciones, tanto 2. como 3. son opciones viables. Cuál es mejor para su caso depende de su caso de uso. Tenga cuidado con la optimización prematura. Solo hazlo si crees que esto es un problema.

Para las bibliotecas que serían utilizadas por terceros, usaría la opción 3.

Peter Knego
fuente
Si usamos Option-2 y declaramos SimpleDateFormatcomo una variable de instancia, podemos usar synchronized blockpara hacerlo seguro para subprocesos. Pero el sonar muestra una advertencia de calamar-AS2885 . ¿Hay alguna forma de resolver el problema de la sonda?
M Faisal Hameed
24

La otra opción es Commons Lang FastDateFormat, pero solo puede usarla para formatear la fecha y no para analizar.

A diferencia de Joda, puede funcionar como un reemplazo directo del formateo. (Actualización: desde v3.3.2, FastDateFormat puede producir un FastDateParser , que es un reemplazo seguro para subprocesos para SimpleDateFormat)

Adam Gent
fuente
8
Desde Commons Lang 3.2, también FastDateFormattiene parse()método
manuna
19

Si está utilizando Java 8, es posible que desee utilizar java.time.format.DateTimeFormatter:

Esta clase es inmutable y segura para subprocesos.

p.ej:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new java.util.Date().toInstant()
                                 .atZone(ZoneId.systemDefault())
                                 .format(formatter);
Pablo Vargas
fuente
6

Commons Lang 3.x ahora tiene FastDateParser y FastDateFormat. Es seguro para subprocesos y más rápido que SimpleDateFormat. También utiliza las mismas especificaciones de patrón de formato / análisis que SimpleDateFormat.

Chas
fuente
Solo está disponible en 3.2+ y no 3.x
Wisteso
4

No use SimpleDateFormat, use DateTimeFormatter de joda-time en su lugar. Es un poco más estricto en el lado del análisis y, por lo tanto, no es un reemplazo para SimpleDateFormat, pero joda-time es mucho más amigable en términos de seguridad y rendimiento.

Jed Wesley-Smith
fuente
3

Yo diría que cree una clase contenedora simple para SimpleDateFormat que sincronice el acceso a parse () y format () y pueda usarse como un reemplazo directo. Más infalible que su opción # 2, menos engorrosa que su opción # 3.

Parece que hacer que SimpleDateFormat no esté sincronizado fue una mala decisión de diseño por parte de los diseñadores de la API de Java; Dudo que alguien espere que format () y parse () necesiten sincronizarse.

Andy
fuente
1

Otra opción es mantener las instancias en una cola segura para subprocesos:

import java.util.concurrent.ArrayBlockingQueue;
private static final int DATE_FORMAT_QUEUE_LEN = 4;
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
private ArrayBlockingQueue<SimpleDateFormat> dateFormatQueue = new ArrayBlockingQueue<SimpleDateFormat>(DATE_FORMAT_QUEUE_LEN);
// thread-safe date time formatting
public String format(Date date) {
    SimpleDateFormat fmt = dateFormatQueue.poll();
    if (fmt == null) {
        fmt = new SimpleDateFormat(DATE_PATTERN);
    }
    String text = fmt.format(date);
    dateFormatQueue.offer(fmt);
    return text;
}
public Date parse(String text) throws ParseException {
    SimpleDateFormat fmt = dateFormatQueue.poll();
    if (fmt == null) {
        fmt = new SimpleDateFormat(DATE_PATTERN);
    }
    Date date = null;
    try {
        date = fmt.parse(text);
    } finally {
        dateFormatQueue.offer(fmt);
    }
    return date;
}

El tamaño de dateFormatQueue debe ser algo cercano al número estimado de subprocesos que pueden llamar a esta función de forma rutinaria al mismo tiempo. En el peor de los casos, donde más subprocesos que este número realmente usan todas las instancias al mismo tiempo, se crearán algunas instancias de SimpleDateFormat que no se pueden devolver a dateFormatQueue porque está lleno. Esto no generará un error, solo incurrirá en la penalización de crear algunos SimpleDateFormat que se usan solo una vez.

SamJ
fuente
1

Acabo de implementar esto con la Opción 3, pero hice algunos cambios en el código:

  • ThreadLocal generalmente debería ser estático
  • Parece más limpio anular initialValue () en lugar de probar if (get () == null)
  • Es posible que desee establecer la configuración regional y la zona horaria a menos que realmente desee la configuración predeterminada (los valores predeterminados son muy propensos a errores con Java)

    private static final ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh", Locale.US);
            sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
            return sdf;
        }
    };
    public String formatDate(Date d) {
        return tl.get().format(d);
    }
mwk
fuente
0

Imagina que tu aplicación tiene un hilo. Entonces, ¿por qué sincronizaría el acceso a la variable SimpleDataFormat?

Vórtice
fuente