Su Intel una lettura volatile non contestata è abbastanza economica. Se consideriamo il seguente semplice caso:
public static long l;
public static void run() {
if (l == -1)
System.exit(-1);
if (l == -2)
System.exit(-1);
}
Utilizzando la capacità di Java 7 di stampare il codice assembly, il metodo run assomiglia a:
# {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
Se guardi i 2 riferimenti a getstatic, il primo implica un caricamento dalla memoria, il secondo salta il caricamento poiché il valore viene riutilizzato dai registri in cui è già caricato (lungo è 64 bit e sul mio laptop a 32 bit utilizza 2 registri).
Se rendiamo volatile la variabile l, l'assemblaggio risultante è diverso.
# {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
In questo caso entrambi i riferimenti getstatici alla variabile l comportano un caricamento dalla memoria, cioè il valore non può essere mantenuto in un registro su più letture volatili. Per garantire che ci sia una lettura atomica, il valore viene letto dalla memoria principale in un registro MMX movsd 0x6fb7b2f0(%ebp),%xmm0
rendendo l'operazione di lettura una singola istruzione (dall'esempio precedente abbiamo visto che il valore a 64 bit normalmente richiederebbe due letture a 32 bit su un sistema a 32 bit).
Quindi il costo complessivo di una lettura volatile sarà più o meno equivalente a un carico di memoria e può essere economico quanto un accesso alla cache L1. Tuttavia, se un altro core sta scrivendo sulla variabile volatile, la riga della cache verrà invalidata richiedendo una memoria principale o forse un accesso alla cache L3. Il costo effettivo dipenderà fortemente dall'architettura della CPU. Anche tra Intel e AMD i protocolli di coerenza della cache sono diversi.