2010-06-23 12 views
10

En un agradable article with some concurrency tips, un ejemplo fue optimizado para las siguientes líneas:Diferencia entre la sincronización de campo lee y volátil

double getBalance() { 
    Account acct = verify(name, password); 
    synchronized(acct) { return acct.balance; } 
} 

Si entiendo que correctamente, el punto de la sincronización es garantizar que el valor de acct.balance que lee este hilo es actual y que las escrituras pendientes en los campos del objeto en acct.balance también se escriben en la memoria principal.

El ejemplo me hizo pensar un poco: ¿no sería más eficiente simplemente declarar acct.balance (es decir, el saldo de campo de la clase Account) como volatile? Debería ser más eficiente, guardar todos los synchronize en accesos a acct.balance y no bloquear el objeto completo acct. ¿Me estoy perdiendo de algo?

+0

Estás en lo correcto, pero el artículo es realmente acerca de algo completamente diferente - reducir el alcance de bloqueo. – gustafc

Respuesta

13

Estás en la correcta. volátil proporciona una garantía de visibilidad. sincronizado proporciona una garantía de visibilidad Y serialización de secciones de códigos protegidos. Para situaciones MUY sencillas, volátil es suficiente, sin embargo, es fácil tener problemas al usar volátiles en lugar de sincronización.

Si se va a suponer que la cuenta tiene una manera de ajustar su equilibrio inestable entonces no es lo suficientemente bueno

public void add(double amount) 
{ 
    balance = balance + amount; 
} 

entonces tenemos un problema si el saldo es volátil con ninguna otra sincronización. Si dos hilos eran para tratar de llamar a añadir() junto usted podría tener una "perdida" de actualización, donde ocurre lo siguiente

Thread1 - Calls add(100) 
Thread2 - Calls add(200) 
Thread1 - Read balance (0) 
Thread2 - Read balance (0) 
Thread1 - Compute new balance (0+100=100) 
Thread2 - Compute new balance (0+200=200) 
Thread1 - Write balance = 100 
Thread2 - Write balance = 200 (WRONG!) 

Obviamente esto es un error, porque los dos hilos leer el valor actual y actualizada de forma independiente y luego escribió de nuevo (leer, computar, escribir). volátil no ayuda aquí, por lo que necesitaría sincronización para asegurar que un hilo completó la actualización completa antes de que el otro hilo comenzara.

En general, si al escribir código creo "puedo usar volátil en lugar de sincronizado", la respuesta podría ser "sí", pero el tiempo/esfuerzo de resolverlo y el peligro de equivocarse no vale la pena el beneficio (rendimiento menor).

Como nota aparte, una Clase de cuenta bien manejada manejaría toda la lógica de sincronización internamente para que las personas que llaman no tengan que preocuparse por ello.

1

Declarar cuenta tan volátil se somete a las siguientes cuestiones y restricciones

1. "Desde otros hilos no pueden ver las variables locales, declarar variables locales volátil es inútil." Además, si intentas declarar una variable volátil en un método, obtendrás un error de compilación en algunos casos.

double getBalance() { volátil Cuenta acct = verificar (nombre, contraseña); // incorrecta .. }

  1. Declarar cuenta tan volátil advierte que el compilador les traiga nuevo cada vez, en lugar de almacenar en caché los registros de. Esto también inhibe ciertas optimizaciones que suponen que ningún otro hilo cambiará los valores inesperadamente.

  2. Si necesita una sincronización para coordinar los cambios en las variables de diferentes hilos, volátil no le garantiza el acceso atómica, ya que el acceso a una variable volátil nunca se mantiene un bloqueo, no es adecuado para los casos en los que queremos leer-actualizar-escribir como una operación atómica. A menos que esté seguro de que acct = verify (nombre, contraseña); es una operación atómica individual, no puede garantizar resultados exceptuados

  3. Si la variable acct es una referencia de objeto, entonces es posible que sea nula. Intentar sincronizar en un objeto nulo arrojará una NullPointerException usando sincronización. (debido a que está sincronizando con eficacia en la referencia, no el objeto real) Donde tan volátil no se queja

  4. su lugar se podría declarar una variable booleana tan volátil como aquí

    someAccountflag booleano volátil privado;

    public void getBalance() { Cuenta de cuenta; while (! SomeAccountflag) { acct = verificar (nombre, contraseña); } }

Nota No se puede declarar someAccountflag como sincronizado, como no se puede sincronizar en una primitiva con sincronizado, sincronizados sólo funciona con variables de objeto, donde como primitivo o variable objeto se puede declarar volátiles

6. Los campos estáticos de clase final no necesitan ser volátiles, JVM se ocupa de este problema. Por lo tanto, el someAccountflag no necesita ser declarado como volátil si es un estático final o puede usar la cuenta de inicialización de singleton perezosa como un objeto singleton y declararlo de la siguiente manera: private final estático AccountSingleton acc_singleton = new AccountSingleton();

+0

Gracias por la respuesta, pero en realidad sugerí no declarar el acct como volátil, pero acct.balance - es decir, el saldo de campo de la clase Cuenta. Eso modifica algunos de tus comentarios. Traté de aclarar la pregunta un poco a ese respecto. –

1

Si hay varios subprocesos que modifican y acceden a los datos, synchronized garantiza la coherencia de los datos entre varios subprocesos.

si un solo hilo está modificando los datos y varios hilos intentan leer el último valor de los datos, use volatile constructo.

Pero para el código anterior, volatile no garantiza la coherencia de la memoria si múltiples threds modifican el equilibrio. AtomicReference con Double tipo cumple su función.

pregunta SE relacionadas:

Difference between volatile and synchronized in Java

Cuestiones relacionadas