threads thread run multiple manage how example java multithreading

java - thread - ¿Es necesario sincronizar la mutación de una colección no segura para subprocesos en un constructor?



synchronize multiple threads java (4)

Si decido usar una colección no segura para subprocesos y sincronizar su acceso, ¿necesito sincronizar alguna mutación en el constructor? Por ejemplo, en el siguiente código, entiendo que la referencia a la lista será visible para todos los subprocesos posteriores a la construcción porque es definitiva. Pero no sé si esto constituye una publicación segura porque el agregado en el constructor no está sincronizado y está agregando una referencia en la matriz elementData de ArrayList, que no es definitiva.

private final List<Object> list; public ListInConstructor() { list = new ArrayList<>(); // synchronize here? list.add(new Object()); } public void mutate() { synchronized (list) { if (list.checkSomething()) { list.mutateSomething(); } } }


Actualización: La especificación del lenguaje Java establece que la congelación que hace visibles los cambios debe estar al final del constructor, lo que significa que su código está correctamente sincronizado, consulte las respuestas de John Vint y Voo .

Sin embargo, también puedes hacer esto, que definitivamente funciona:

public ListInConstructor() { List<Object> tmp = new ArrayList<>(); tmp.add(new Object()); this.list = tmp; }

Aquí mutamos el objeto de la lista antes de asignarlo al campo final , por lo que la asignación garantizará que cualquier cambio realizado en la lista también será visible.

17.5. semántica de campo final

El modelo de uso para los campos finales es simple: establece los campos finales para un objeto en el constructor de ese objeto; y no escriba una referencia al objeto que se está construyendo en un lugar donde otro hilo pueda verlo antes de que el constructor del objeto finalice. Si se sigue esto, entonces, cuando el objeto sea visto por otro hilo, ese hilo siempre verá la versión correctamente construida de los campos finales de ese objeto. También verá versiones de cualquier objeto o matriz a la que hagan referencia aquellos campos finales que estén al menos tan actualizados como los campos finales.

La oración resaltada le da una garantía de que esta solución funcionará. Aunque, como se señaló al comienzo de la respuesta, el original también tiene que funcionar, pero dejaré esta respuesta aquí ya que la especificación es un poco confusa. Y porque este "truco" también funciona cuando se establecen campos no finales pero volatile (desde cualquier contexto, no solo constructores).


Debido a que el objeto no es inherentemente inmutable, debe publicar el objeto de forma segura. Mientras haga esto, no es necesario realizar las mutaciones en el constructor en un bloque synchronized .

Los objetos pueden ser "publicados de manera segura" de varias maneras. Un ejemplo es pasarlos a otro hilo a través de una cola correctamente sincronizada. Para más detalles, vea Java Concurrencia en la práctica, sección 3.5.3 "Modalidades de publicación seguras" y 3.5.4 "Objetos efectivamente inmutables"


Ok, esto es lo que JLS §17.5.1 tiene que decir sobre el tema.

Ante todo:

Sea o un objeto, y c sea un constructor para o en el que se escribe un campo final f. Una acción de congelación en el campo final f o o tiene lugar cuando c sale, ya sea de forma normal o abrupta .

Así que sabemos que en nuestro código:

public ListInConstructor() { list = new ArrayList<>(); list.add(new Object()); } // the freeze action happens here!

Así que ahora la parte interesante:

Dada una escritura w, una congelación f, una acción a (que no es una lectura de un campo final), una lectura r1 del campo final congelada por f, y una lectura r2 tal que hb (w, f), hb ( f, a), mc (a, r1) y desreferencias (r1, r2), luego, al determinar qué valores puede ver r2, consideramos hb (w, r2).

Así que vamos a hacer esto una pieza a la vez:

Tenemos hb (w, f), lo que significa que escribimos en el campo final antes de abandonar el constructor.

r1 es la lectura del campo final y las desreferencias (r1, r2). Esto significa que r1 lee el campo final y r2 lee un valor de este campo final.

Además, tenemos una acción (una lectura o escritura, pero no una lectura del campo final) que tiene hb (f, a) y mc (a, r1). Esto significa que la acción ocurre después del constructor, pero la lectura r1 la puede ver después.

Y, en consecuencia, indica que "consideramos hb (w, r2)", lo que significa que la escritura debe suceder antes de la lectura a un valor del campo final que se leyó con r1.

Así que, tal como lo veo, está claro que el objeto agregado a la lista tendrá que ser visible por cualquier hilo que pueda leer la list .

En una nota al margen: HotSpot implementa la semántica del campo final colocando una barrera de memoria al final de cualquier constructor que contenga un campo final, garantizando así esta propiedad en cualquier caso. Otra cuestión es si es solo una optimización (es mejor hacer solo una barrera y eso, tan lejos de la escritura como sea posible).


Según el JLS

El modelo de uso para los campos finales es simple: establece los campos finales para un objeto en el constructor de ese objeto; y no escriba una referencia al objeto que se está construyendo en un lugar donde otro hilo pueda verlo antes de que el constructor del objeto finalice.

Dado que la escritura en la Lista se produce antes de que el constructor lo complete, está seguro de mutar la lista sin sincronización adicional.

Edición: Basado en el comentario de Voo, lo haré editar con la inclusión de la congelación del campo final.

Así que leyendo más en 17.5.1 hay esta entrada

Dada una escritura w, una congelación f, una acción a (que no es una lectura de un campo final), una lectura r1 del campo final congelada por f, y una lectura r2 tal que hb (w, f), hb ( f, a), mc (a, r1) y desreferencias (r1, r2),

Interpreto esto como la acción para modificar la matriz que se produce, antes de la anulación posterior de r2 que es la lectura no sincronizada una vez finalizada la congelación (existe el constructor).