tutorial - swing java ejemplos




AtomicReferenceFieldUpdater: métodos para establecer, obtener, comparar y establecer semántica (2)

Como se explica en la documentación del paquete para atomics (en general, no los actualizadores específicamente):

Los efectos de memoria para accesos y actualizaciones de atómicos generalmente siguen las reglas para volátiles, [...]:

  • get tiene los efectos de memoria de leer una variable volatile .
  • set tiene los efectos de memoria de escribir (asignar) una variable volatile .
  • [...]
  • compareAndSet y todas las demás operaciones de lectura y actualización, como getAndIncrement tienen los efectos de memoria tanto de la lectura como de la escritura de variables volatile .

¿Qué problema está tratando de resolver un compareAndSet un atómico? ¿Por qué usar (por ejemplo) atomicInteger.compareAndSet(1,2) lugar de if(volatileInt == 1) { volatileInt = 2; } if(volatileInt == 1) { volatileInt = 2; } ? No se trata de resolver ningún problema con lecturas concurrentes, ya que éstas ya están a cargo de un volatile regular. (Una lectura o escritura "volátil" es lo mismo que una lectura o escritura "atómica". Una lectura concurrente solo sería un problema si ocurriera en medio de una escritura, o si las declaraciones se reordenaran o se optimizaran de alguna manera problemática; pero volatile ya previene esas cosas.) El único problema que compareAndSet resuelve es que, en el enfoque volatileInt , algún otro hilo puede aparecer con una escritura concurrente, entre cuando leemos volatileInt ( volatileInt == 1 ) y cuando le escribimos ( volatileInt = 2 ). compareAndSet resuelve este problema bloqueando cualquier escritura de la competencia durante ese tiempo.

Esto es igualmente cierto en el caso específico de los "actualizadores" ( AtomicReferenceFieldUpdater etc.): volatile lecturas volatile aún son duras. La única limitación de los actualizadores ' compareAndSet ' es que, en lugar de "bloquear escrituras en competencia" como escribí anteriormente, solo bloquean escrituras en competencia desde la misma instancia de AtomicReferenceFieldUpdater ; no pueden protegerlo cuando está actualizando simultáneamente un campo volatile (o, para el caso, cuando está utilizando simultáneamente varios AtomicReferenceFieldUpdater s para actualizar el mismo campo volatile ). (Por cierto, dependiendo de cómo lo mires, lo mismo se AtomicReference y su parentesco: si tuvieras que actualizar sus campos de una manera que los pasara por alto, ellos no podrían protegerte. La diferencia es que en realidad AtomicReference una AtomicReference posee su campo, y es private , por lo que no es necesario advertirle de que no lo modifique de alguna manera por medios externos.)

Entonces, para responder a su pregunta: Sí, puede continuar leyendo campos volatile con las mismas garantías de atomicidad contra lecturas parciales / inconsistentes, contra declaraciones reordenadas, etc.

Editado para agregar (6 de diciembre): cualquier persona que esté particularmente interesada en este tema probablemente estará interesada en la discusión inmediatamente a continuación. Me pidieron que actualizara la respuesta para aclarar los puntos más destacados de esa discusión:

  • Creo que el punto más importante para agregar es que lo anterior es mi propia interpretación de la documentación. Estoy bastante seguro de que lo he entendido correctamente y de que ninguna otra interpretación tiene sentido; y puedo, si lo desea, discutir el punto en longitud ;-); pero ni yo ni nadie más ha producido ninguna referencia a ningún documento autoritario que aborde este punto de manera más explícita que los dos documentos mencionados en la pregunta en sí (el Javadoc de la clase y Java concurrencia en la práctica ) y el documento mencionado en mi respuesta original a arriba (el Javadoc del paquete).

  • El siguiente punto más importante, creo, es que aunque la documentación de AtomicReferenceUpdater dice que no es seguro mezclar compareAndSet con una escritura volátil, creo que en plataformas típicas es realmente seguro. Es inseguro sólo en el caso general. Digo esto debido al siguiente comentario de la documentación del paquete:

    Las especificaciones de estos métodos permiten que las implementaciones empleen instrucciones atómicas eficientes a nivel de máquina que están disponibles en los procesadores contemporáneos. Sin embargo, en algunas plataformas, el soporte puede implicar algún tipo de bloqueo interno. Por lo tanto, no se garantiza estrictamente que los métodos no sean bloqueantes: un hilo puede bloquearse de forma transitoria antes de realizar la operación.

    Asi que:

    • En una implementación JDK típica para un procesador moderno, AtomicReference.set simplemente usa una escritura volátil, ya que AtomicReference.compareAndSet usa una operación de comparación e intercambio que es atómica con respecto a las escrituras volátiles. AtomicReferenceUpdater.set es necesariamente más complejo que AtomicReference.set , porque tiene que usar una lógica de reflexión para actualizar un campo en otro objeto, pero sostengo que esa es la única razón por la que es más complejo. Una implementación típica llama Unsafe.putObjectVolatile , que es una escritura volátil por nombre más largo.
    • Pero no todas las plataformas admiten este enfoque, y si no lo hacen, se permite el bloqueo. A riesgo de simplificar en exceso, entiendo que esto significa que, en compareAndSet se puede implementar la compareAndSet el compareAndSet una clase atómica aplicando (más o menos) la synchronized a un método que usa get y set directa. Pero para que esto funcione, el set también debe estar synchronized , por la razón explicada en mi respuesta original anterior; es decir, no puede ser simplemente una escritura volátil, porque entonces podría modificar el campo después de que compareAndSet haya llamado a get pero antes de que compareAndSet set llamadas.
    • No hace falta decir que el uso que hace mi respuesta original de la frase "bloqueo" no debe tomarse literalmente, ya que en una plataforma típica no se produce ninguna necesidad de bloqueo.
  • En la implementación JDK 1.6.0_05 de Sun de java.util.concurrent.ConcurrentLinkedQueue<E> , encontramos esto:

    private static class Node<E> {
        private volatile E item;
        private volatile Node<E> next;
        private static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater =
            AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
        private static final AtomicReferenceFieldUpdater<Node, Object> itemUpdater =
            AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "item");
        Node(E x) { item = x; }
        Node(E x, Node<E> n) { item = x; next = n; }
        E getItem() { return item; }
        boolean casItem(E cmp, E val)
            { return itemUpdater.compareAndSet(this, cmp, val); }
        void setItem(E val) { itemUpdater.set(this, val); }
        Node<E> getNext() { return next; }
        boolean casNext(Node<E> cmp, Node<E> val)
            { return nextUpdater.compareAndSet(this, cmp, val); }
        void setNext(Node<E> val) { nextUpdater.set(this, val); }
    }
    

    (nota: espacios en blanco ajustados para la compacidad), donde, una vez que se ha construido una instancia, no hay escrituras volátiles (es decir, todas las escrituras son a través de AtomicReferenceFieldUpdater.compareAndSet o AtomicReferenceFieldUpdater.set , pero las lecturas volátiles parecen usarse libremente, sin llamada única a AtomicReferenceFieldUpdater.get . Las versiones posteriores de JDK 1.6 se cambiaron para usar Unsafe directamente (esto había sucedido con el JDK 1.6.0_27 de Oracle), pero las discusiones en la lista de correo JSR 166 atribuyen este cambio a consideraciones de rendimiento en lugar de a cualquier duda sobre la corrección de la implementación anterior.

    • Pero debo señalar que esto no es una autoridad a prueba de balas. Para mayor comodidad, escribo sobre la "implementación de Sun" como si hubiera sido una cosa unitaria, pero mi punto anterior anterior hace evidente que las implementaciones de JDK para diferentes plataformas pueden tener que hacer las cosas de manera diferente. El código anterior me parece haber sido escrito de una manera neutral para la plataforma, ya que evita las escrituras volátiles en favor de las llamadas a AtomicReferenceFieldUpdater.set ; pero alguien que no acepta mi interpretación de un punto puede no aceptar mi interpretación del otro, y puede argumentar que el código anterior no está destinado a ser seguro para todas las plataformas.
    • Otra debilidad de esta autoridad es que, aunque Node parece permitir que se realicen lecturas volátiles al mismo tiempo que las llamadas a AtomicReferenceFieldUpdater.compareAndSet , es una clase privada; y no he realizado ninguna prueba de que su propietario ( ConcurrentLinkedQueue ) realmente haga esas llamadas sin sus propias precauciones. (Pero aunque no he probado la afirmación, dudo que alguien la cuestione).

Consulte los comentarios a continuación para conocer los antecedentes de este apéndice y para obtener más información.

De la documentación de AtomicReferenceFieldUpdater Java :

Tenga en cuenta que las garantías del método compareAndSet en esta clase son más débiles que en otras clases atómicas. Debido a que esta clase no puede garantizar que todos los usos del campo sean apropiados para fines de acceso atómico, puede garantizar la atomicidad y la semántica volátil solo con respecto a otras invocaciones de compareAndSet y set .

Esto significa que no puedo hacer escrituras volátiles normales junto con compareAndSet , pero tengo que usar set lugar. No menciona nada sobre get .

¿ compareAndSet significa que todavía puedo leer campos volátiles con las mismas garantías de atomicidad? ¿Todas las escrituras anteriores al set o compareAndSet son visibles para todos los que han leído el campo volátil?

¿O tengo que usar get en AtomicReferenceFieldUpdater lugar de lecturas volátiles en el campo?

Por favor publique referencias si las tiene.

Gracias.

EDITAR:

Desde Java Concurrency in Practice , lo único que dicen:

Las garantías de atomicidad para las clases de actualización son más débiles que para las clases atómicas regulares porque no puede garantizar que los campos subyacentes no se modifiquen directamente; los métodos compareAndSet y aritméticos garantizan la atomicidad solo con respecto a otros subprocesos que utilizan los métodos de actualización de campo atómico.

Nuevamente, no se menciona cómo los otros subprocesos deben leer estos campos volátiles.

Además, ¿tengo razón al suponer que "modificado directamente" es una escritura volátil regular?


Esta no será una respuesta exacta de la pregunta:

Ni la explicación, ni la intención se ve clara en la documentación. Si la idea fuera eludir el ordenamiento global, también conocido como escritura volátil en arquitecturas que lo permiten [como IBM Power o ARM] y simplemente exponer el comportamiento de CAS (LoadLinked / StoreCondition) SIN cercas, sería un esfuerzo sorprendente y una fuente de confusión.

El CAS de sun.misc.Unsafe no tiene ninguna especificación ni garantías de pedido (se conoce como sucede antes), pero java.util.atomic ... sí. Así que en el modelo más débil java.util.atomic impl. Requeriría las cercas necesarias para seguir la especificación de Java en este caso.

Asumiendo que las clases de Updater en realidad carecen de vallas. Si lo hacen, la lectura volátil del campo (sin usar get) devolverá el valor de actualización, es decir, claramente get() es necesario. Dado que no habrá garantías de pedido, las tiendas anteriores podrían no propagarse (en modelos débiles). El hardware x86 / Sparc TSO garantiza la especificación de java.

Sin embargo, eso también significa que se puede reordenar CAS con las siguientes lecturas no volátiles. Hay una nota interesante de la cola java.util.concurrent.SynchronousQueue :

        // Note: item and mode fields don't need to be volatile
        // since they are always written before, and read after,
        // other volatile/atomic operations.

Todas las operaciones atómicas mencionadas son exactamente CAS de AtomicReferenceFieldUpdater. Eso implicaría la falta o la grabación entre las lecturas Y escrituras normales y AtomicReferenceFieldUpdater.CAS, es decir, actuar como escritura volátil.

        s.item = null;   // forget item
        s.waiter = null; // forget thread

        //....

        while ((p = head) != null && p != past && p.isCancelled())
            casHead(p, p.next);

Solo CAS, no escribe volátil.

Dada la condición anterior, concluiría que AtomicXXXFieldUpdater expone la misma semántica que sus homólogos de AtomicXXX.





jsr166