AtomicInteger lazySet frente a conjunto

116

¿Cuál es la diferencia entre los métodos lazySety setde AtomicInteger? La documentación no tiene mucho que decir sobre lazySet:

Eventualmente se establece en el valor dado.

Parece que el valor almacenado no se establecerá inmediatamente en el valor deseado, sino que se programará para establecerse en el futuro. Pero, ¿cuál es el uso práctico de este método? ¿Algún ejemplo?

Cheok Yan Cheng
fuente

Respuestas:

114

Citado directamente de "JDK-6275329: Agregar métodos lazySet a clases atómicas" :

Como probablemente el último seguimiento de JSR166 para Mustang, agregamos un método "lazySet" a las clases Atomic (AtomicInteger, AtomicReference, etc.). Este es un método de nicho que a veces es útil cuando se ajusta el código utilizando estructuras de datos sin bloqueo. La semántica es que se garantiza que la escritura no se reordenará con ninguna escritura anterior, pero se puede reordenar con operaciones posteriores (o, de manera equivalente, podría no ser visible para otros subprocesos) hasta que ocurra alguna otra acción de escritura o sincronización volátil).

El caso de uso principal es anular campos de nodos en estructuras de datos sin bloqueo únicamente con el fin de evitar la retención de basura a largo plazo; se aplica cuando es inofensivo si otros subprocesos ven valores no nulos durante un tiempo, pero le gustaría asegurarse de que las estructuras eventualmente sean compatibles con GC. En tales casos, puede obtener un mejor rendimiento evitando los costos de escritura volátil nula. También hay algunos otros casos de uso en esta línea para atómicos no basados ​​en referencias, por lo que el método es compatible con todas las clases de AtomicX.

Para las personas a las que les gusta pensar en estas operaciones en términos de barreras a nivel de máquina en multiprocesadores comunes, lazySet proporciona una barrera tienda-tienda anterior (que no es operativa o es muy barata en las plataformas actuales), pero sin barrera de carga de tienda (que suele ser la parte más cara de una escritura volátil).

bostezo
fuente
14
¿Alguien podría simplificarlo para el resto de nosotros? :(
Gaurav
14
Lazy es la versión no volátil (por ejemplo, no se garantiza que el cambio de estado sea visible para todos los hilos que tienen el Atomic*alcance).
bostezo
63
lo que no entiendo es por qué javadoc es tan pobre al respecto.
Felipe
8
Estoy seguro de que eventualmente lograrán cambiarlo. Boom boom.
MMJZ
3
para aquellos que quieran saber más sobre la barrera de carga / tienda y por qué la barrera de tienda-tienda es más barata que la barrera de carga de tienda. Aquí hay un artículo fácil de entender al respecto. Mechanical-sympathy.blogspot.com/2011/07/…
Kin Cheung
15

lazySet se puede usar para la comunicación entre subprocesos rmw, porque xchg es atómico, en cuanto a la visibilidad, cuando el proceso del subproceso del escritor modifica la ubicación de una línea de caché, el procesador del subproceso del lector lo verá en la siguiente lectura, porque el protocolo de coherencia de caché de la CPU intel garantizará LazySet funciona, pero la línea de caché se actualizará en la siguiente lectura, nuevamente, la CPU tiene que ser lo suficientemente moderna.

http://sc.tamu.edu/systems/eos/nehalem.pdf Para Nehalem, que es una plataforma multiprocesador, los procesadores tienen la capacidad de "espiar" (espiar) el bus de direcciones para los accesos de otros procesadores a la memoria del sistema y a sus cachés internos. Usan esta capacidad de espionaje para mantener sus cachés internos consistentes tanto con la memoria del sistema como con los cachés en otros procesadores interconectados. Si al espiar un procesador detecta que otro procesador tiene la intención de escribir en una ubicación de memoria que actualmente tiene almacenada en caché en estado Compartido, el procesador de espionaje invalidará su bloque de caché y lo obligará a realizar un llenado de línea de caché la próxima vez que acceda a la misma ubicación de memoria. .

oracle hotspot jdk para arquitectura de cpu x86->

lazySet == unsafe.putOrderedLong == xchg rw (instrucción asm que sirve como una barrera blanda que cuesta 20 ciclos en la cpu intel nehelem)

en x86 (x86_64), dicha barrera es mucho más barata en términos de rendimiento que volátil o AtomicLong getAndAdd,

En un escenario de cola de un productor, un consumidor, la barrera suave xchg puede forzar la línea de códigos antes del lazySet (secuencia + 1) para que el hilo del productor ocurra ANTES de cualquier código del hilo del consumidor que consumirá (trabajará) los nuevos datos, por supuesto El hilo del consumidor deberá verificar atómicamente que la secuencia del productor se incrementó exactamente en uno usando un compareAndSet (secuencia, secuencia + 1).

Seguí el código fuente de Hotspot para encontrar la asignación exacta del lazySet al código cpp: http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/unsafe. cpp Unsafe_setOrderedLong -> Definición de SET_FIELD_VOLATILE -> OrderAccess: release_store_fence. Para x86_64, OrderAccess: release_store_fence se define como el uso de la instrucción xchg.

Puede ver cómo se define exactamente en jdk7 (doug lea está trabajando en algunas cosas nuevas para JDK 8): http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/4fc084dac61e/src/os_cpu/ linux_x86 / vm / orderAccess_linux_x86.inline.hpp

también puede usar hdis para desmontar el ensamblado del código lazySet en acción.

Hay otra pregunta relacionada: ¿Necesitamos mfence cuando usamos xchg?

Chuleta de cerdo
fuente
5
Es difícil entender a qué se refiere aquí. ¿Puede aclarar su punto?
Paul Bellora
3
"lazySet == unsafe.putOrderedLong == xchg rw (instrucción asm que sirve como una barrera blanda que cuesta 20 ciclos en la CPU intel nehelem) en x86 (x86_64) dicha barrera es mucho más barata en términos de rendimiento que volátil o AtomicLong getAndAdd" -> Esto no es cierto a mi leal saber y entender. lazySet / putOrdered es un MOV a una dirección, por lo que el libro de cocina de JMM lo describe como no operativo en x86.
Nitsan Wakart
11

Puede encontrar una discusión más amplia sobre los orígenes y la utilidad de lazySet y el putOrdered subyacente aquí: http://psy-lob-saw.blogspot.co.uk/2012/12/atomiclazyset-is-performance-win-for.html

Para resumir: lazySet es una escritura volátil débil en el sentido de que actúa como una tienda-tienda y no como una barrera de carga. Esto se reduce a que lazySet se compila con JIT en una instrucción MOV que el compilador no puede reordenar en lugar de la instrucción significativamente más cara que se usa para un conjunto volátil.

Al leer el valor siempre terminas haciendo una lectura volátil (con un Atomic * .get () en cualquier caso).

lazySet ofrece a un solo escritor un mecanismo de escritura volátil consistente, es decir, es perfectamente legítimo que un solo escritor use lazySet para incrementar un contador, varios subprocesos que incrementen el mismo contador tendrán que resolver las escrituras en competencia usando CAS, que es exactamente lo que sucede bajo las carátulas de Atomic * para incAndGet.

Nitsan Wakart
fuente
exactamente, ¿por qué no podemos decir que esta es una StoreStorebarrera simple , pero no una StoreLoad?
Eugene
8

Del resumen del paquete concurrente-atómico

lazySet tiene los efectos de memoria de escribir (asignar) una variable volátil excepto que permite reordenar con acciones de memoria posteriores (pero no previas) que no imponen restricciones de reordenamiento con escrituras no volátiles ordinarias. Entre otros contextos de uso, lazySet puede aplicarse al anular, en aras de la recolección de basura, una referencia a la que nunca se vuelve a acceder.

Si sientes curiosidad por lazySet, entonces también te debes otras explicaciones

Los efectos de memoria para accesos y actualizaciones de atomics generalmente siguen las reglas para volátiles, como se indica en la sección 17.4 de The Java ™ Language Specification.

get tiene los efectos de memoria de leer una variable volátil.

set tiene los efectos de memoria de escribir (asignar) una variable volátil.

lazySet tiene los efectos de memoria de escribir (asignar) una variable volátil, excepto que permite reordenar con acciones de memoria posteriores (pero no anteriores) que no imponen restricciones de reordenamiento con escrituras no volátiles ordinarias. Entre otros contextos de uso, lazySet puede aplicarse al anular, por el bien de la recolección de basura, una referencia a la que nunca se vuelve a acceder.

DébilCompareAndSet lee atómicamente y escribe condicionalmente una variable pero no crea ningún orden de sucesos anteriores, por lo que no ofrece garantías con respecto a lecturas y escrituras anteriores o posteriores de cualquier variable que no sea el objetivo del débilCompareAndSet.

compareAndSet y todas las demás operaciones de lectura y actualización, como getAndIncrement, tienen los efectos de memoria de leer y escribir variables volátiles.

Ajeet Ganga
fuente
4

Aquí está mi entendimiento, corríjame si me equivoco: puede pensar en lazySet()como "semi" volátil: es básicamente una variable no volátil en términos de lectura por otros hilos, es decir, el valor establecido por lazySet puede no ser visible para otros hilos. Pero se vuelve volátil cuando ocurre otra operación de escritura (puede ser de otros hilos). El único impacto de lazySet que puedo imaginar escompareAndSet . Entonces, si lo usa lazySet(), get()de otros subprocesos aún puede obtener el valor anterior, pero compareAndSet()siempre tendrá el nuevo valor ya que es una operación de escritura.

jyluo
fuente
1
no te refieres compareAndSet?
Dave Moten
2

Re: intento de simplificarlo -

Puede pensar en esto como una forma de tratar un campo volátil como si no fuera volátil para una operación de tienda en particular (por ejemplo: ref = null;).

Eso no es del todo exacto, pero debería ser suficiente para que pudieras tomar una decisión entre "Está bien, realmente no me importa" y "Mmmm, déjame pensarlo un poco".

Paul Mclachlan
fuente