Pregunta

Leyendo " Java Concurrency In Practice " ;, hay esta parte en la sección 3.5:

public Holder holder;
public void initialize() {
     holder = new Holder(42);
}

Además del peligro obvio para la seguridad de los hilos al crear dos instancias de Holder , el libro afirma que puede ocurrir un posible problema de publicación.

Además, para una clase de Titular como

public Holder {
    int n;
    public Holder(int n) { this.n = n };
    public void assertSanity() {
        if(n != n)
             throw new AssertionError("This statement is false.");
    }
}

se puede lanzar un AssertionError !

¿Cómo es esto posible? La única forma en que puedo pensar que puede permitir un comportamiento tan ridículo es si el constructor Holder no estuviera bloqueando, por lo que se crearía una referencia a la instancia mientras el código del constructor aún se ejecuta en un hilo diferente.

¿Es esto posible?

¿Fue útil?

Solución

La razón por la que esto es posible es que Java tiene un modelo de memoria débil. No garantiza el orden de lectura y escritura.

Este problema en particular se puede reproducir con los siguientes dos fragmentos de código que representan dos subprocesos.

Tema 1:

someStaticVariable = new Holder(42);

Tema 2:

someStaticVariable.assertSanity(); // can throw

En la superficie parece imposible que esto pueda ocurrir. Para comprender por qué puede suceder esto, debe pasar la sintaxis de Java y pasar a un nivel mucho más bajo. Si observa el código del subproceso 1, puede dividirse esencialmente en una serie de escrituras y asignaciones de memoria:

  1. Asignar memoria a puntero1
  2. Escriba 42 en el puntero 1 en el desplazamiento 0
  3. escriba el puntero1 en alguna variable estática

Debido a que Java tiene un modelo de memoria débil, es perfectamente posible que el código se ejecute realmente en el siguiente orden desde la perspectiva del subproceso 2:

  1. Asignar memoria a puntero1
  2. escriba el puntero1 en alguna variable estática
  3. Escriba 42 en el puntero 1 en el desplazamiento 0

¿De miedo? Sí, pero puede suceder.

Lo que esto significa es que el hilo 2 ahora puede llamar a assertSanity antes de que n haya obtenido el valor 42. Es posible para el valor n para leer dos veces durante assertSanity , una vez antes de que se complete la operación # 3 y una vez después y, por lo tanto, vea dos valores diferentes y lance una excepción.

EDIT

Según Jon Skeet , el AssertionError aún podría ocurrir con Java 8 a menos que el campo sea final.

Otros consejos

El modelo de memoria Java usado es tal que la asignación a la referencia Holder podría hacerse visible antes de la asignación a la variable dentro del objeto.

Sin embargo, el modelo de memoria más reciente que entró en vigor a partir de Java 5 lo hace imposible, al menos para los campos finales: todas las asignaciones dentro de un constructor " suceden antes de " Cualquier asignación de la referencia al nuevo objeto a una variable. Consulte la Sección de especificación del lenguaje Java 17.4 para más detalles, pero aquí está el fragmento más relevante:

  

Se considera que un objeto es   completamente inicializado cuando su   Constructor de acabados. Un hilo que   Solo se puede ver una referencia a un objeto.   después de que el objeto ha sido completamente   inicializado está garantizado para ver el   valores correctamente inicializados para ese   campos finales del objeto

Por lo tanto, tu ejemplo aún podría fallar, ya que n no es final, pero debería estar bien si haces que n sea final.

Por supuesto el:

if (n != n)

ciertamente podría fallar para las variables no finales, suponiendo que el compilador JIT no lo optimice, si las operaciones son:

  • Recupera LHS: n
  • Recupera RHS: n
  • Compare LHS y RHS

entonces el valor podría cambiar entre las dos búsquedas.

Bueno, en el libro indica para el primer bloque de código que:

  

El problema aquí no es el Titular   clase en sí, pero que el titular es   no publicado adecuadamente. Sin embargo,   El titular puede ser inmune a impropio   publicación declarando el campo n   Ser final, lo que haría Holder.   inmutable; ver Sección 3.5.2

Y para el segundo bloque de código:

  

Porque no se usó la sincronización   Para hacer visible el Titular a otro.   hilos, decimos que el Titular no era   debidamente publicado. Dos cosas pueden ir   mal con incorrectamente publicado   objetos. Otros hilos podrían ver un   valor obsoleto para el campo del titular, y   así ver una referencia nula u otra   valor más antiguo a pesar de que un valor tiene   Se ha colocado en el titular. Pero mucho peor,   otros hilos podrian ver un up-todate   valor para la referencia del titular, pero   valores añejos para el estado de la   Titular [16] Para hacer las cosas aún menos.   predecible, un hilo puede ver un rancio   Valor la primera vez que lee un campo.   y luego un valor más actualizado de la   La próxima vez, por eso assertSanity.   puede lanzar AssertionError.

Creo que JaredPar ha explicado esto en su comentario.

(Nota: No busque votos aquí: las respuestas permiten obtener información más detallada que los comentarios).

El problema básico es que, sin la sincronización adecuada, la forma en que se escriben en la memoria puede manifestarse en diferentes subprocesos. El ejemplo clásico:

a = 1;
b = 2;

Si lo hace en un hilo, un segundo hilo puede ver b establecido en 2 antes de que a se establezca en 1. Además, es posible que haya una cantidad ilimitada de tiempo entre un segundo hilo que ve una de esas variables. actualizado y la otra variable se está actualizando.

mirando esto desde una perspectiva sensata, si asume que la declaración

if (n! = n)

es atómico (lo que creo que es razonable, pero no lo sé con seguridad), entonces la excepción de la afirmación nunca se podría lanzar.

Este ejemplo se incluye en " Una referencia al objeto que contiene el campo final no escapó al constructor "

Cuando creas una instancia de un nuevo objeto Holder con el nuevo operador,

  1. la máquina virtual Java primero asignará (al menos) espacio suficiente en el montón para contener todas las variables de instancia declaradas en el Titular y sus superclases.
  2. En segundo lugar, la máquina virtual inicializará todas las variables de instancia a sus valores iniciales predeterminados. 3.c Tercero, la máquina virtual invocará el método en la clase Holder.

consulte lo anterior: http://www.artima.com/designtechniques/initializationP.html

Supongamos que: el primer subproceso comienza a las 10:00 am, llama instatied al objeto Holder haciendo la llamada del nuevo Holer (42), 1) la máquina virtual Java primero asignará (al menos) espacio suficiente en el montón para contener todas las variables de instancia declaradas en el Titular y sus superclases. - Será 10:01 tiempo 2) En segundo lugar, la máquina virtual inicializará todas las variables de instancia a sus valores iniciales predeterminados: comenzará a las 10:02 hora 3) En tercer lugar, la máquina virtual invocará el método en la clase Holder .-- comenzará a las 10:04 hora

Ahora Thread2 comenzó en - > 10:02:01 hora, y hará una llamada a assertSanity () 10:03, en ese momento n se inició con el valor predeterminado de Cero, Segundo hilo que lee los datos obsoletos.

// publicación insegura titular público titular;

si hace que el titular final público resuelva este problema

o

int int privado; si haces la final privada n; Se resuelve este problema.

consulte: http: // www. cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html en la sección ¿Cómo funcionan los campos finales en el nuevo JMM?

También me sorprendió mucho ese ejemplo. Encontré un sitio web que explica el tema a fondo y los lectores podrían encontrarlo útil: https: // www.securecoding.cert.org/confluence/display/java/TSM03-J.+Do+not+publish+partially+initialized+objects

Editar: El texto relevante del enlace dice:

  

el JMM permite a los compiladores asignar memoria para el nuevo Ayudante   objeto y para asignar una referencia a esa memoria para el campo de ayuda   Antes de inicializar el nuevo objeto de ayuda. En otras palabras, la   el compilador puede reordenar la escritura en el campo de la instancia auxiliar y la   escritura que inicializa el objeto de ayuda (es decir, this.n = n) para que   lo primero ocurre primero. Esto puede exponer una ventana de carrera durante la cual   Otros hilos pueden observar un objeto de ayuda parcialmente inicializado.   instancia.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top