2012-04-20 14 views
10

Quiero asegurarme de que entiendo correctamente el comportamiento de los objetos 'efectivamente inmutables' de acuerdo con el modelo de memoria de Java.Objeto efectivamente inmutable

Digamos que tenemos una clase mutable, que queremos publicar como una forma efectiva inmutable:

class Outworld { 
    // This MAY be accessed by multiple threads 
    public static volatile MutableLong published; 
} 

// This class is mutable 
class MutableLong { 
    private long value; 

    public MutableLong(long value) { 
    this.value = value; 
    } 

    public void increment() { 
    value++; 
    } 

    public long get() { 
    return value; 
    } 
} 

hacemos lo siguiente:

// Create a mutable object and modify it 
MutableLong val = new MutableLong(1); 
val.increment(); 
val.increment(); 
// No more modifications 
// UPDATED: Let's say for this example we are completely sure 
//   that no one will ever call increment() since now 

// Publish it safely and consider Effectively Immutable 
Outworld.published = val; 

La pregunta es: ¿Java Memory Model garantiza que todos los hilos DEBEN tener Outworld.published.get() == 3?

De acuerdo con Java Concurrency In Practice esto debería ser cierto, pero por favor corríjanme si me equivoco.

3.5.3. Lenguajes de publicación seguros

Para publicar un objeto de forma segura, tanto la referencia al objeto como el estado del objeto deben hacerse visibles para otros hilos al mismo tiempo. Un objeto correctamente construido se puede publicar de forma segura por:
- Inicializando una referencia de objeto desde un inicializador estático;
- Almacenar una referencia en un campo volátil o AtomicReference;
- Almacenar una referencia en un campo final de un objeto construido correctamente; o
- Almacenar una referencia en un campo que esté debidamente protegido por un bloqueo.

3.5.4. Objetos efectivamente inmutables

Publicación segura Objetos efectivamente inmutables se pueden utilizar de forma segura por cualquier hilo sin sincronización adicional.

+0

Muestre el [* static initializer *] (http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) que establece el estado del objeto antes de que se haga visible su referencia. . – trashgod

Respuesta

5

Sí. Las operaciones de escritura en el MutableLong son seguidas por una relación happens-before (en el volátil) antes de la lectura.

(Es posible que un hilo lee Outworld.published y se lo pasa a otro hilo en condiciones de riesgo. En teoría, que podría ver estado anterior. En la práctica, no veo que esto ocurra.)

+1

Su comentario (2 ° párrafo) parece incompatible con su respuesta (1er párrafo) y lo que sucede antes se relaciona con la lectura volátil. ¿Puedes elaborar? – assylias

+2

Supongamos que el thread T1 safe se publica en T2. Hay un * happening-before * de T1 a T2. Pero si el mismo objeto se publica de forma insegura de T2 a T3, entonces no hay * happen-before * de T2 a T3 y, por lo tanto, tampoco * happens-before * de T1 a T3. –

2

El La pregunta es: ¿Java Memory Model garantiza que todos los hilos DEBEN tener Outworld.published.get() == 3?

La respuesta corta es no. Porque otros subprocesos pueden tener acceso al Outworld.published antes de que se haya leído.

Después del momento en que se realizó Outworld.published = val;, a condición de que no se realicen otras modificaciones con el val - sí - siempre será 3.

Pero si algún hilo realiza val.increment, entonces su valor puede ser diferente para otros hilos.

+0

Sí, estoy hablando de una situación en la que podemos considerar este objeto como Efectivamente Inmutable; es decir, estamos completamente seguros de que ningún otro hilo llamará 'increment()' en él. Actualicé un ejemplo para ser más específico. –

4

Hay un par de condiciones que deben cumplirse para el modelo de memoria de Java para garantizar que Outworld.published.get() == 3:

  • el fragmento de código que envió, que crea e incrementa el MutableLong, a continuación, establece el campo Outworld.published, debe pasar con visibilidad entre los pasos. Una forma de lograr esto trivialmente es tener todo ese código ejecutándose en un único subproceso, garantizando "como semántica de serie". Supongo que eso es lo que pretendías, pero pensé que valía la pena señalarlo.
  • lecturas de Outworld.published debe tener sucede-después de semántica de la asignación. Un ejemplo de esto podría ser tener el mismo subproceso ejecutar Outworld.published = val;luego ejecutar otros hilos que podrían leer el valor. Esto garantizaría "como si fuera serial" semántica, evitando el reordenamiento de las lecturas antes de la asignación.

Si usted es capaz de proporcionar esas garantías, entonces el JMM se garantiza todas las discusiones ver Outworld.published.get() == 3.


Sin embargo, si usted está interesado en el asesoramiento de diseño programa general en esta área, sigue leyendo.

Para la garantía de que no hay otros hilos vez ver un valor diferente para Outworld.published.get(), usted (el desarrollador) tienen que garantizar que su programa no modifica el valor de ninguna manera. Ya sea ejecutando posteriormente Outworld.published = differentVal; o Outworld.published.increment();. Mientras que es posible garantizar, puede ser mucho más fácil si el diseño de su código para evitar tanto el objeto mutable, y el uso de un campo no static final como un punto de acceso global para múltiples hilos:

  • vez de publicación MutableLong, copie los valores relevantes en una nueva instancia de una clase diferente, cuyo estado no se puede modificar. Por ejemplo, introduzca la clase ImmutableLong, que asigna value a un campo final en construcción, y no tiene un método increment().
  • en lugar de múltiples subprocesos que acceden a un campo no final estático, pase el objeto como un parámetro a sus implementaciones Callable/Runnable. Esto evitará la posibilidad de que un hilo deshonesto reasigne el valor e interfiera con los demás, y es más fácil razonar que la reasignación de campos estáticos. (Es cierto que si se trata de un código heredado, es más fácil decirlo que hacerlo).
+0

Estoy completamente de acuerdo con lo que dices. La sincronización es demasiado fácil de equivocarse. Lo evitaría a toda costa. La mutabilidad compartida no es una forma segura de diseñar un algoritmo o programa. Creo que cada programador debería dedicar algo de tiempo al estudio de conceptos de programación funcional y aplicarlos al mundo de oo. Definitivamente vale la pena. – bennidi