En Intel, una lectura volátil no contenida es bastante barata. Si consideramos el siguiente caso simple:
public static long l;
public static void run() {
if (l == -1)
System.exit(-1);
if (l == -2)
System.exit(-1);
}
Usando la capacidad de Java 7 para imprimir código ensamblador, el método de ejecución se parece a lo siguiente:
# {method} 'run2' '()V' in 'Test2'
# [sp+0x10] (sp of caller)
0xb396ce80: mov %eax,-0x3000(%esp)
0xb396ce87: push %ebp
0xb396ce88: sub $0x8,%esp ;*synchronization entry
; - Test2::run2@-1 (line 33)
0xb396ce8e: mov $0xffffffff,%ecx
0xb396ce93: mov $0xffffffff,%ebx
0xb396ce98: mov $0x6fa2b2f0,%esi ; {oop('Test2')}
0xb396ce9d: mov 0x150(%esi),%ebp
0xb396cea3: mov 0x154(%esi),%edi ;*getstatic l
; - Test2::run@0 (line 33)
0xb396cea9: cmp %ecx,%ebp
0xb396ceab: jne 0xb396ceaf
0xb396cead: cmp %ebx,%edi
0xb396ceaf: je 0xb396cece ;*getstatic l
; - Test2::run@14 (line 37)
0xb396ceb1: mov $0xfffffffe,%ecx
0xb396ceb6: mov $0xffffffff,%ebx
0xb396cebb: cmp %ecx,%ebp
0xb396cebd: jne 0xb396cec1
0xb396cebf: cmp %ebx,%edi
0xb396cec1: je 0xb396ceeb ;*return
; - Test2::run@28 (line 40)
0xb396cec3: add $0x8,%esp
0xb396cec6: pop %ebp
0xb396cec7: test %eax,0xb7732000 ; {poll_return}
;... lines removed
Si observa las 2 referencias a getstatic, la primera implica una carga de la memoria, la segunda omite la carga ya que el valor se reutiliza de los registros en los que ya está cargado (el largo es de 64 bits y en mi computadora portátil de 32 bits utiliza 2 registros).
Si hacemos volátil la variable l, el ensamblado resultante es diferente.
# {method} 'run2' '()V' in 'Test2'
# [sp+0x10] (sp of caller)
0xb3ab9340: mov %eax,-0x3000(%esp)
0xb3ab9347: push %ebp
0xb3ab9348: sub $0x8,%esp ;*synchronization entry
; - Test2::run2@-1 (line 32)
0xb3ab934e: mov $0xffffffff,%ecx
0xb3ab9353: mov $0xffffffff,%ebx
0xb3ab9358: mov $0x150,%ebp
0xb3ab935d: movsd 0x6fb7b2f0(%ebp),%xmm0 ; {oop('Test2')}
0xb3ab9365: movd %xmm0,%eax
0xb3ab9369: psrlq $0x20,%xmm0
0xb3ab936e: movd %xmm0,%edx ;*getstatic l
; - Test2::run@0 (line 32)
0xb3ab9372: cmp %ecx,%eax
0xb3ab9374: jne 0xb3ab9378
0xb3ab9376: cmp %ebx,%edx
0xb3ab9378: je 0xb3ab93ac
0xb3ab937a: mov $0xfffffffe,%ecx
0xb3ab937f: mov $0xffffffff,%ebx
0xb3ab9384: movsd 0x6fb7b2f0(%ebp),%xmm0 ; {oop('Test2')}
0xb3ab938c: movd %xmm0,%ebp
0xb3ab9390: psrlq $0x20,%xmm0
0xb3ab9395: movd %xmm0,%edi ;*getstatic l
; - Test2::run@14 (line 36)
0xb3ab9399: cmp %ecx,%ebp
0xb3ab939b: jne 0xb3ab939f
0xb3ab939d: cmp %ebx,%edi
0xb3ab939f: je 0xb3ab93ba ;*return
;... lines removed
En este caso, las dos referencias getstatic a la variable l implican una carga desde la memoria, es decir, el valor no se puede mantener en un registro a través de múltiples lecturas volátiles. Para garantizar que haya una lectura atómica, el valor se lee desde la memoria principal en un registro MMX, lo que movsd 0x6fb7b2f0(%ebp),%xmm0
hace que la operación de lectura sea una sola instrucción (del ejemplo anterior vimos que el valor de 64 bits normalmente requeriría dos lecturas de 32 bits en un sistema de 32 bits).
Por lo tanto, el costo total de una lectura volátil será aproximadamente equivalente a una carga de memoria y puede ser tan barato como un acceso a la caché L1. Sin embargo, si otro núcleo está escribiendo en la variable volátil, la línea de caché se invalidará, lo que requiere una memoria principal o quizás un acceso a la caché L3. El costo real dependerá en gran medida de la arquitectura de la CPU. Incluso entre Intel y AMD, los protocolos de coherencia de caché son diferentes.