2011-02-15 15 views
14

Estoy buscando en la implementación de ConcurrentHashMap y algo me hace confundirme.ConcurrentHashMap instrucciones de reorden?

/* Specialized implementations of map methods */ 

     V get(Object key, int hash) { 
      if (count != 0) { // read-volatile 
       HashEntry<K,V> e = getFirst(hash); 
       while (e != null) { 
        if (e.hash == hash && key.equals(e.key)) { 
         V v = e.value; 
         if (v != null) 
          return v; 
         return readValueUnderLock(e); // recheck 
        } 
        e = e.next; 
       } 
      } 
      return null; 
     } 

y

/** 
    * Reads value field of an entry under lock. Called if value 
    * field ever appears to be null. This is possible only if a 
    * compiler happens to reorder a HashEntry initialization with 
    * its table assignment, which is legal under memory model 
    * but is not known to ever occur. 
    */ 
    V readValueUnderLock(HashEntry<K,V> e) { 
     lock(); 
     try { 
      return e.value; 
     } finally { 
      unlock(); 
     } 
    } 

y constructor HashEntry

/** 
    * ConcurrentHashMap list entry. Note that this is never exported 
    * out as a user-visible Map.Entry. 
    * 
    * Because the value field is volatile, not final, it is legal wrt 
    * the Java Memory Model for an unsynchronized reader to see null 
    * instead of initial value when read via a data race. Although a 
    * reordering leading to this is not likely to ever actually 
    * occur, the Segment.readValueUnderLock method is used as a 
    * backup in case a null (pre-initialized) value is ever seen in 
    * an unsynchronized access method. 
    */ 
    static final class HashEntry<K,V> { 
    final K key; 
      final int hash; 
      volatile V value; 
      final HashEntry<K,V> next; 

      HashEntry(K key, int hash, HashEntry<K,V> next, V value) { 
       this.key = key; 
       this.hash = hash; 
       this.next = next; 
       this.value = value; 
      } 

poner en práctica

tab[index] = new HashEntry<K,V>(key, hash, first, value); 

confundí al comentario HashEntry, como JSR-133, una vez HashEntry es const ructed, todos los campos finales serán visibles para todos los demás hilos, valor campo es volátil, por lo que creo que es visible para otros hilos también ??? . Otro punto es el reordenamiento que dijo: la referencia del objeto HashEntry se puede asignar a tabulación [...] antes de que esté completamente construida (por lo que el resultado es que otros subprocesos pueden ver esta entrada pero e.value puede ser nulo)?

Actualización: Leo this artículo y es bueno. Pero necesito para cuidar de un caso como este

ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); 

thread1: 

Person p=new Person("name","student");   
queue.offer(new Person()); 

thread2: 
Person p = queue.poll(); 

¿Existe la posibilidad de que Thread2 recibir un objeto Persona sin terminar-construcción al igual que HashEntry en

pestaña [índice] = new HashEntry (clave , hash, primero, valor); ?

+0

Con valor volátil que puede garantizar la visibilidad de todos los otros hilos. – dimitrisli

+0

sí, entonces todos los campos son visibles por todos los demás hilos, ¿por qué solo tenemos que preocuparnos de que el valor de 'valor' sea nulo? – secmask

Respuesta

7

Para aquellos interesados ​​en una respuesta del Doug Lea sobre este tema, recientemente se explica la razón de readValueUnderLock

Esto es en respuesta a alguien que tenía la pregunta:

en el ConcurrentHashMap el conseguir método no requiere "readValueUnderLock" porque una carrera eliminar no hace que el valor sea nulo. El valor nunca se vuelve nulo en el del subproceso de eliminación. esto significa es posible devolver un valor para la clave, incluso si la eliminación de hilo (en la misma clave) tiene progresado hasta el punto de clonación las partes anteriores de la lista. Este está bien siempre que sea el efecto deseado.

Pero esto significa que "readValueUnderLock" es no requerido para el modelo de memoria NUEVO.

Sin embargo, para el modelo de memoria OLD una put puede ver el valor nulo debido al reordenamiento (Raro pero posible).

Es mi entendimiento correcto.

Respuesta:

No del todo. Tiene razón de que nunca debe ser llamado. Sin embargo, el JLS/JMM se puede leer como no absolutamente prohibiendo que se llame a debido a las deficiencias en requeridos relaciones de ordenación entre finales vs volátiles establecidos en los constructores (clave es final, el valor es volátil), wrt la lee por hilos utilizando los objetos de entrada . (En JMM-ese, ordenando restricciones para finales caen fuera de la relación synchronizes-with.) Ese es el problema al que hace referencia el comentario (pegado debajo). Nadie ha pensado alguna vez en cualquier resquicio práctica que un procesador/compilador podría encontrar para producir un valor nulo leer, y puede ser demostrable que no existe ninguno (y quizá algún día un JLS/revisión JMM va a llenar los vacíos para aclarar esto), pero Bill Pugh una vez sugirió que pusiéramos de todos modos solo por el bien de siendo conservadoramente pedante correcto. En retrospectiva, no estoy tan seguro de que esta era una buena idea, ya que lleva a las personas a presentar teorías exóticas .

todo puede ser visto here

+0

¡Gracias, John! Todavía no tengo idea de por qué la especificación no permite que los campos volátiles tengan semántica de las finales durante las llamadas c-tor. Ambos requieren una barrera de memoria para ser implementada (por lo que no hay una barrera especial para los volátiles durante las llamadas de c-tor, si esto no se escapa). – bestsss

+0

@bestsss No podría estar más de acuerdo con usted. No puedo imaginar ninguna buena razón (e incluso el mismo Doug) para que la inicialización volátil en un constructor sea diferente de las finales. –

1

Por lo que yo entiendo memoria del modelo, escribir a la variable volátil se garantiza que sea visible por todos los posteriores (como se define por fin de sincronización) lee de esa variable.

Sin embargo, nada garantiza que lectura de e.value en get() es posterior a escribir de value en el constructor (ya que no hay sincronizado con relaciones entre estas acciones), de manera que el modelo de memoria permite que este tipo de reordenar y sincronización explícita en el caso del valor null es necesaria para garantizar que leemos el valor correcto.

UPDATE: El nuevo modelo de memoria garantiza que cualquier escritura a no volátil variables antes de escribir a la variable volátil puede ser visto por otros hilos después de la lectura posterior de esa variable volátil, pero no viceversa.

Aquí está el extracto relacionado de The Java Memory Model by Jeremy Manson, William Pugh and Sarita Adve:

5.1.1 Bloqueo de engrosamiento.
...
Todo esto es simplemente una manera indirecta de decir que accesos a las variables normales pueden reordenarse con un siguiente lectura volátil o adquisición de bloqueo, o un escritura volátil o bloqueo de edición precedente.Esto implica que los accesos normales se pueden mover dentro de las regiones de bloqueo, pero (en su mayor parte) no están fuera de ellos;

Por lo tanto, la asignación del objeto construido se puede reordenar con la variable de escritura dentro de un constructor, por lo que es necesaria la verificación en cuestión.

+0

la entrada, en sí misma, no debe publicarse en el objeto [] antes de que se establezca entry.value. El cheque es superfluo b/c cualquier asignación antes del valor del constructor debe completarse (y no puede reordenarse). Si tuvieran que hacerlo más explícito debería haber construido la entrada, establecer el valor y luego establecer la entrada en el Objeto [] (aunque ahora está prácticamente compilado de la misma manera) – bestsss

+0

@bestsss: ¿Puede probar su afirmación mediante algunas referencias a la memoria? ¿modelo? – axtavt

+0

@axtavt, lo intento * El antiguo modelo de memoria permitía que las escrituras volátiles se reordenasen con lecturas y escrituras no volátiles, lo que no era coherente con la mayoría de las intuiciones de desarrolladores sobre volátiles y por lo tanto causaba confusión. * [Parte del prefacio]; – bestsss

1

confundí al comentario HashEntry, como JSR-133, una vez HashEntry se construido, todos los campos finales serán visible a todos los otros hilos, el valor campo es volátil, así que creo que visible para otros hilos también? .

Otros hilos verán valor como bien pero ... La assigment de la entrada (en el Object []) se realiza después de la inicialización y bajo llave. Entonces, si algún hilo ve null, intentará leer el valor debajo del bloqueo.

Otro punto, es el nuevo pedido se dijo : HashEntry referencia de objeto puede ser asignado a ficha [...] antes de que sea completa construidos (por lo demás es consecuencia hilos pueden ver esta entrada pero el correo .value puede ser nulo)?

No, no puede b/c no es una asignación volátil (value) y que significa todas las demás operaciones deben establecerse antes de la mano (es decir, no reordenadas). También tenga en cuenta que la creación de objetos java es de 2 fases, creando un objeto vacío con campos cero/nulo (como el uso del c-tor predeterminado) y luego llamando al método <init> (que es el constructor). El objeto no puede asignarse a nada antes de completar la llamada al constructor y su última asignación de value (para garantizar el orden correcto también conocido como sucede antes)

+0

, así que se crea HashEntry y todos los campos están establecidos (volátil y final) por , luego el objeto se asigna a la pestaña [..], no veo ningún caso que haga que e.value sea nulo? – secmask

+0

@secmask, respondo así: yo mismo habría omitido el cheque, ahora se necesita un reloj extra de la CPU (ya que la rama nunca se toma y la CPU lo pronostica correctamente). Cualquiera puede vivir con eso. – bestsss

Cuestiones relacionadas