modificadores modificador metodo definicion acceso java arrays multithreading volatile

metodo - modificadores de acceso java



¿Necesitamos sincronizar el acceso a una matriz cuando la variable de la matriz es volátil? (4)

private volatile Object[] objects = new Object[100];

Solo haces referencia de objects para ser volatile esta manera. No es el contenido de la instancia de matriz que está asociado.

Pregunta: ¿Necesito sincronizar tales lecturas y escrituras para asegurar la consistencia de la memoria?

Sí.

no sería útil desde el punto de vista del rendimiento si JVM proporciona algún tipo de garantía de consistencia de memoria al escribir en una matriz

considere usar colecciones como CopyOnWriteArrayList (o su propio contenedor de arreglos con alguna implementación de Lock dentro de los mutadores y los métodos de lectura).

La plataforma Java también tiene Vector (obsoleto con diseño defectuoso) y synchronized List (lento para muchos escenarios), pero no recomiendo usarlos.

PD: una buena idea más de @SashaSalauyou

Tengo una clase que contiene una referencia volátil a una matriz:

private volatile Object[] objects = new Object[100];

Ahora, puedo garantizar que solo un hilo (escríbalo como writer ) puede escribir en la matriz. Por ejemplo,

objects[10] = new Object();

Todos los demás subprocesos solo leerán los valores escritos por el subproceso del writer .

Pregunta: ¿Necesito sincronizar tales lecturas y escrituras para asegurar la consistencia de la memoria?

Supongo que sí debería. Porque no sería útil desde el punto de vista del rendimiento si JVM proporciona algún tipo de garantía de consistencia de memoria al escribir en una matriz. Pero no estoy seguro de eso. No encontró nada útil en la documentación.


Puedes usar AtomicReferenceArray :

final AtomicReferenceArray<Object> objects = new AtomicReferenceArray<>(100); // writer objects.set(10, new Object()); // reader Object obj = objects.get(10);

Esto asegurará las actualizaciones atómicas y la coherencia de las operaciones de lectura / escritura antes de que ocurra, de la misma manera que si cada elemento de la matriz fuera volatile .


Sí, necesita sincronizar los accesos a los elementos de una matriz volátil.

Otras personas ya se han CopyOnWriteArrayList cómo probablemente puedas usar CopyOnWriteArrayList o AtomicReferenceArray , así que me AtomicReferenceArray en una dirección ligeramente diferente. También recomendaría leer jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html por uno de los grandes colaboradores de JMM, Jeremy Manson.

Ahora, puedo garantizar que el único hilo (llamarlo escritor) puede escribir en la matriz como se muestra a continuación:

Si puede dar garantías de un solo escritor o no, no está relacionado de ninguna manera con la palabra clave volatile . Creo que no tenías eso en mente, pero lo estoy aclarando, para que otros lectores no tengan la impresión equivocada ( creo que hay un juego de palabras de la carrera de datos que puede hacerse con esa frase ).

Todos los demás subprocesos solo leerán los valores escritos por el subproceso del escritor.

Sí, pero al igual que su intuición lo guió correctamente, esto se mantiene solo para el valor de la referencia a la matriz. Esto significa que, a menos que esté escribiendo referencias de matriz a la variable volatile , no obtendrá la parte de escritura del contrato de lectura / escritura volatile .

Lo que esto significa es que o quieres hacer algo como

objects[i] = newObj; objects = objects;

que es feo y horrible de muchas maneras diferentes. O desea publicar una nueva matriz cada vez que su escritor realice una actualización, por ejemplo,

Object[] newObjects = new Object[100]; // populate values in newObjects, make sure that newObjects IS NOT published yet // publish newObjects through the volatile variable objects = newObjects;

que no es un caso de uso muy común.

Tenga en cuenta que, a diferencia de la configuración de elementos de matriz, que no proporciona una semántica de escritura- volatile , obtener elementos de matriz (con newObj = objects[i]; ) proporciona una semántica de lectura- volatile , porque está desreferiéndose a la matriz :)

Porque no sería útil desde el punto de vista del rendimiento si JVM proporciona algún tipo de garantía de consistencia de memoria al escribir en una matriz. Pero no estoy seguro de eso.

Al igual que aludir, garantizar que el cercado de memoria requerido para la semántica volatile sea ​​muy costoso, y si agrega un intercambio falso a la mezcla, se vuelve aún peor.

No encontró nada útil en la documentación.

Puede suponer con seguridad que la semántica volatile para las referencias de matriz es exactamente la misma que la semántica volatile para las referencias no de matriz, lo que no es sorprendente en absoluto, teniendo en cuenta cómo las matrices (incluso las primitivas) son objetos.


Según JLS § 17.4.5 - Happens-before Order :

Se pueden ordenar dos acciones por una relación de suceso-antes . Si una acción ocurre antes de otra, entonces la primera es visible y ordenada antes de la segunda.

[...]

Se produce una escritura en un campo volatile , antes de cada lectura posterior de ese campo.

La relación pasa-antes es bastante fuerte. Significa que si el subproceso A escribe en una variable volatile , y cualquier subproceso B más tarde lee la variable, entonces el subproceso B está garantizado para ver el cambio en la propia variable volatile , así como todos los demás subprocesos de cambio A realizados antes de establecer la variable volatile , incluyendo a cualquier otro objeto, sean o no volatile .

Sin embargo, esto no es suficiente!

Los objects[10] = new Object(); asignación de elementos objects[10] = new Object(); No es una escritura de los objects variables. Es solo una lectura de la variable para determinar la matriz a la que apunta, seguida de una escritura en una variable diferente que está contenida dentro del objeto de matriz ubicado en otro lugar en la memoria. No se establece una relación de suceso antes de las meras lecturas de volatile variables volatile , por lo que el código no es seguro.

Como lo señala @DimitarDimitrov, puede evitar esto haciendo una escritura ficticia en la variable de objects . Cada par de operaciones - los objects = objects; reasignación por el hilo del escritor junto con un foo = objects[x]; búsqueda por un subproceso del lector: define una relación actualizada de suceso antes , y por lo tanto "publicará" todos los últimos cambios realizados por el subproceso de escritura en el subproceso del lector. Eso puede funcionar, pero requiere disciplina y no es elegante.

Pero hay un problema más sutil con eso: incluso si el subproceso del lector ve el valor actualizado del elemento de la matriz que aún no garantiza que vea los campos del objeto referido por ese elemento correctamente, porque es posible el siguiente orden :

  1. Writer crea algún objeto foo .
  2. El escritor establece objects[x] = foo;
  3. Reader revisa los objects[x] y ve la referencia al nuevo objeto foo (lo que puede hacer , aunque no se garantiza que lo haga, ya que aún no hay una relación antes de que ocurra ).
  4. El escritor hace objects = objects;

Desafortunadamente, esto no define la relación formal antes del suceso, ya que la lectura de la variable volátil (3) apareció antes de la escritura de la variable volátil (4). Aunque el lector puede ver que los objects[x] son el objeto foo por casualidad, esto no significa que los campos de foo se publiquen de manera segura , por lo que el lector puede, en teoría, ver el nuevo objeto, ¡pero con los valores incorrectos! Para resolverlo, los objetos que está compartiendo entre los hilos que utilizan esta técnica necesitarían tener todos los campos final o volatile o sincronizados de otra manera. Si todos los objetos son String , por ejemplo, estarás bien, pero de lo contrario, es muy fácil cometer errores con esto. (Gracias @Holger por señalar esto).

Aquí hay algunas alternativas menos escamosas:

  • Las clases de matrices concurrentes como AtomicReferenceArray existen para proporcionar matrices en las que cada elemento se comporta como si fuera volatile . Esto es mucho más fácil de usar correctamente, porque asegura que si un lector ve el valor del elemento de la matriz actualizado, también ve correctamente el objeto al que se refiere ese elemento.

  • Puede envolver todos los accesos a la matriz en bloques sincronizados, sincronizando en algún objeto compartido:

    // writer synchronized (aSharedObject) { objects[x] = foo; }

    // reader synchronized (aSharedObject) { bar = objects[x]; }

    Al igual que volatile , el uso synchronized crea una relación de suceso antes . (Todo lo que hace un subproceso antes de liberar el bloqueo de sincronización de un objeto ocurre antes de que cualquier otro subproceso adquiera el bloqueo de sincronización del mismo objeto). Si lo hace, su matriz no necesita ser volatile .

  • Considera si una matriz es realmente lo que necesitas aquí. No ha dicho para qué son estos hilos de escritor y lector, pero si desea algún tipo de cola de productor-consumidor, entonces la clase que realmente necesita es un BlockingQueue o un Executor . Debería echar un vistazo a las clases de concurrencia de Java para ver si una de ellas ya hace lo que necesita, porque si la hace, seguramente será más fácil de usar correctamente que volatile .