2010-03-24 11 views
5

Estoy usando joda debido a su buena reputación con respecto al multi threading. Recorre grandes distancias para hacer que el manejo de la fecha de múltiples hilos sea eficiente, por ejemplo, haciendo que todos los objetos de Fecha/Hora/Fecha y Hora sean inmutables.Objeto parcialmente construido/Multi threading

Pero aquí hay una situación en la que no estoy seguro de si Joda realmente está haciendo lo correcto. Probablemente sí, pero estoy muy interesado en ver una explicación.

Cuando un toString() de un DateTime se está llamando Joda hace lo siguiente:

/* org.joda.time.base.AbstractInstant */ 
public String toString() { 
    return ISODateTimeFormat.dateTime().print(this); 
} 

Todos los formateadores son seguros para subprocesos (que inmutables también), pero lo que está sobre el formateador de fábrica:

private static DateTimeFormatter dt; 

/* org.joda.time.format.ISODateTimeFormat */ 
public static DateTimeFormatter dateTime() { 
    if (dt == null) { 
     dt = new DateTimeFormatterBuilder() 
      .append(date()) 
      .append(tTime()) 
      .toFormatter(); 
    } 
    return dt; 
} 

Este es un patrón común en aplicaciones de subproceso único, pero se sabe que es propenso a errores en un entorno multiproceso.

veo los siguientes riesgos: condición

  • Carrera durante el check nula -> peor de los casos: dos objetos se crean.

No hay problema, ya que esto es únicamente un objeto auxiliar (a diferencia de una situación normal patrón singleton), uno se guardan en dt, el otro está perdido y habrá basura recogida tarde o temprano.

  • la variable estática podría apuntar a un objeto construido parcialmente antes de la objec se ha terminado la inicialización

(antes de llamar loco, leer acerca de una situación similar en este Wikipedia article.)

Así ¿Cómo se asegura Joda de que ningún formateador creado parcialmente se publique en esta variable estática?

¡Gracias por sus explicaciones!

Reto

Respuesta

4

Dijiste que los formateadores son de solo lectura. Si usan solo campos finales (no leí una fuente de formateador), entonces en la 3ra edición de la especificación del lenguaje Java están protegidos de la creación de objetos parciales por "Semántica de campo final". No revisé la edición 2-nd JSL y no estoy seguro, si dicha iniciación es correcta en esa edición.

Consulte el capítulo 17.5 y 17.5.1 en JLS. Construiré una "cadena de eventos" para la relación requerida antes-antes.

Antes que nada, en algún lugar del constructor hay una escritura en el campo final en el formateador. Es la escritura w. Cuando el constructor finaliza, una acción de "congelación" se lleva a cabo. Vamos a llamarlo f. En algún lugar posterior en el orden del programa (después de regresar del constructor, tal vez algunos otros métodos y regresar de toFormatter) hay una escritura en el campo dt. Vamos a darle a esto escribir un nombre a. Esta escritura (a) es posterior a la acción de congelación (f) en el "orden del programa" (orden en la ejecución de un solo subproceso) y por lo tanto sucede f antes de a (hb (f, a)) solo por definición de JLS. Uff, inicialización completa ... :)

Algunas veces más tarde, en otro hilo, ocurre una llamada al formato dateTime(). En ese momento necesitamos dos lecturas. El primero de los dos se lee de la variable final en el objeto formateador. Vamos a llamarlo r2 (para ser coherente con el JLS). El segundo de los dos es una lectura del "esto" para el formateador. Esto ocurre durante la llamada al método dateTime() cuando se lee el campo dt. Y llamémosle lectura r1. ¿Qué tenemos ahora? Lea r1 vio algunos escribir en el dt. Considero que esa escritura fue la acción a del párrafo anterior (solo un hilo escribió ese campo, solo por simplicidad). Como r1 vea la escritura a, entonces hay mc (a, r1) (relación "Cadena de memoria", definición de la primera cláusula). La secuencia actual no inicializó un formateador, lo lee en la acción r2 y ve una "dirección" de un formateador leída en la acción r1. Por lo tanto, por definición, hay una desreferencia (r1, r2) (otra acción que ordena desde JLS).

Tenemos escribir antes de congelar, hb (w, f). Nos hemos congelado antes de la asignación del dt, hb (f, a). Tenemos una lectura de dt, mc (a, r1). Y tenemos una cadena de desreferencia entre r1 y r2, dereferencias (r1, r2). Todo esto conduce a una relación de pasar antes de hb (w, r2) solo por definición de JLS. Además, por definición, hb (d, w) donde d es una escritura del valor predeterminado para el campo final en el objeto. Por lo tanto, leer r2 no puede ver escribir w y debe ver escribir r2 (la única escritura en el campo desde un código de programa).

Igual es el orden para un acceso de campo más indirecto (campo final del objeto almacenado en el campo final, etc ...).

¡Pero eso no es todo! No hay acceso a un objeto parcialmente construido. Pero hay un error más interesante. En ausencia de una sincronación explícita, dateTime() puede devolver nulo. No creo que ese comportamiento se pueda observar en la práctica, pero la 3ª edición de JLS no previene dicho comportamiento. La primera lectura del campo dt en el método puede ver un valor inicializado por otro hilo, pero la segunda lectura de dt puede ver un "valor de escritura de defalut". No sucede, antes de que existan relaciones para prevenirlo. Tal comportamiento posible es específico de la 3ra edición, la segunda edición tiene una "escritura en la memoria principal"/"lectura de la memoria principal" que no permite que un hilo vea los valores de la variable retrocediendo en el tiempo.

+0

+1. esa es otra razón por la cual los objetos inmutables son 'más simples' en el modelo concurrente de Java. Sin embargo, no estoy seguro acerca de su último párrafo, consulte JLS 17.4.4: "La escritura del valor predeterminado (cero, falso o nulo) para cada variable se sincroniza, con la primera acción en cada hilo". Es necesario que la "escritura del valor predeterminado" ocurra antes de la lectura de dt. – irreputable

+0

Sí, una escritura de valor predeterminado es anterior a la lectura de dt. Esto evita que el hilo lea el valor de "basura". Pero una escritura del campo en otro hilo (que inicializó el campo dt) no está en relación hb con la lectura dt. Por lo tanto, podemos ver cualquiera de esas dos escrituras (por defecto o desde un hilo que inicializó dt) en cualquier momento. El único momento en que debemos leer un valor de inicialización predeterminado es verificar el requisito de casualidad. Pero en ese momento solo cometemos una sola lectura con el valor predeterminado y en la siguiente iteración, dicho, esa secuencia ve una escritura de un objeto para dt (solo para la primera lectura en el método). – maxkar

-1

OMI el peor de los casos es no dos objetos personales creados, sino varios (tantos como hay hilos de llamada dateTime(), para ser exactos). Como dateTime() no está sincronizado y dt no es definitivo ni volátil, no se garantiza que un cambio en su valor en un subproceso sea visible para otros subprocesos. Por lo tanto, incluso después de que un hilo inicializado dt, cualquier número de otros hilos todavía puede ver la referencia como nula, así felizmente crear nuevos objetos.

Aparte de eso, como explicaron otros, un objeto parcialmente creado no puede ser publicado por dateTime(). Tampoco puede una referencia parcialmente cambiada (= colgando), ya que se garantiza que las actualizaciones del valor de referencia serán atómicas.

0

Esto es un poco de una no-respuesta, pero la explicación más simple para

Entonces, ¿cómo asegurarse de que no Joda parcialmente formateador creado se publica en esta variable estática?

puede ser que no garanticen nada, y los desarrolladores no se dieron cuenta de que podría tratarse de un error o de que no valía la pena la sincronización.

0

I asked a similar question en la lista de correo de Joda en 2007, aunque no encontré las respuestas para ser concluyentes y evité el tiempo de Joda como resultado, para bien o para mal.

La versión 3 de Java Language Spec garantiza que las actualizaciones de referencia de objeto son atómicas, independientemente de si son de 32 bits o de 64 bits.Esto, combinado con los argumentos descritos anteriormente, hace que el código Joda sea seguro para las IMO (vea java.sun.com/docs/books/jls/third_edition/html/memory.html#17.7)

IIRC, versión 2 de el JLS no incluía la misma aclaración explícita sobre las referencias de objeto, es decir, solo se garantizaba que los refs de 32 bits fueran atómicos, por lo que si usaba una JVM de 64 bits no había garantía de que funcionaría. En ese momento estaba usando Java 1.4, que era anterior a JLS v3.