2010-02-14 17 views
14

En la clase a continuación, ¿es seguro el método getIt() y por qué?sincronización de hilos java

public class X { 
    private long myVar; 
    public void setIt(long var){ 
    myVar = var; 
    } 
    public long getIt() { 
    return myVar; 
    } 
} 
+0

¿Es esta tarea? ¿O una pregunta de entrevista? – finnw

+3

@finnw: Siempre que prefiera :) ¿Cuál es la diferencia ...? si no contribuiste ...? ¿Quieres que cambie las etiquetas? –

+0

Tenía curiosidad porque me hicieron una pregunta similar en una entrevista. No hay necesidad de cambiar las etiquetas. – finnw

Respuesta

22

No es seguro para subprocesos. Las variables del tipo long y double en Java se tratan como dos variables separadas de 32 bits. Un hilo puede estar escribiendo y ha escrito la mitad del valor cuando otro hilo lee ambas mitades. En esta situación, el lector vería un valor que nunca se suponía que existiera.

Para que esto sea seguro para subprocesos que puede o bien declarar myVar como volatile (Java 1.5 o posterior) o hacen tanto setIt y getItsynchronized.

Tenga en cuenta que incluso si myVar era un int de 32 bits, aún podría encontrarse con problemas de subprocesamiento donde un subproceso podría estar leyendo un valor desactualizado que otro subproceso ha cambiado. Esto podría ocurrir porque el valor ha sido guardado en la memoria caché por la CPU. Para resolver esto, nuevamente necesita declarar myVar como volatile (Java 1.5 o posterior) o hacer ambos setIt y getItsynchronized.

También vale la pena señalar que si está utilizando el resultado de getIt en una llamada posterior setIt, p. x.setIt(x.getIt() * 2), entonces es probable que quieren synchronize a través de ambas llamadas:

synchronized(x) 
{ 
    x.setIt(x.getIt() * 2); 
} 

Sin la sincronización adicional, otro hilo podría cambiar el valor entre las getIt y setIt llamadas causando el valor del otro hilo que se pierde.

+0

+1 Este es un hecho que la mayoría de los desarrolladores desconocen. En lugar de volátil, también podría usar una de las clases Atomic * (que se encuentra en java.util.concurrent) para expresar mejor su intención. – helpermethod

6

No, no lo es. Al menos, no en plataformas que carecen de acceso a memoria atómica de 64 bits.

Supongamos que el subproceso A llama al setIt, copia 32 bits en la memoria donde está el valor de la copia de seguridad y luego se lo elimina antes de que pueda copiar los otros 32 bits.

Luego el hilo B llama al getIt.

+7

Incluso en plataformas que proporcionan acceso de memoria atómica de 64 bits, debe sincronizar los métodos get y set. O tienes que hacer que el miembro sea volátil. Ver mi respuesta para más detalles. – tangens

+0

Tu respuesta es correcta, pero demasiado específica. Hay razones mucho más generales en cuanto a por qué esto no está sincronizado - vea @tangens answer. –

2

Debe ser, y generalmente lo es, pero no es garantizado para que sea seguro para subprocesos. Puede haber problemas con diferentes núcleos que tienen diferentes versiones en la memoria caché de la CPU, o la tienda/recuperación no es atómica para todas las arquitecturas. Use la clase AtomicLong.

-4

Dado que es un método de solo lectura. Debe sincronizar el método set.

EDIT: Veo por qué el método get necesita sincronizarse también. Buen trabajo explicando a Phil Ross.

+10

Ambos métodos deben estar sincronizados para que esto definitivamente sea seguro para los hilos.Otherwiese, el método get podría producir una versión parcialmente modificada. –

3

No, no, porque los largos no son atómicos en java, por lo que un hilo podría haber escrito 32 bits del largo en el método setIt, y luego el get podría leer el valor, y luego establecerlo. bits.

Así que el resultado final es que getIt devuelve un valor que nunca fue válido.

9

Esto no es seguro para subprocesos.Incluso si su plataforma garantiza escrituras atómicas de long, la falta de synchronized hace posible que un hilo llame a setIt() e incluso después de que esta llamada haya finalizado es posible que otro hilo pueda llamar al getIt() y esta llamada pueda devolver el valor anterior de myVar.

La palabra clave synchronized hace más que un acceso exclusivo de un hilo a un bloque o un método. También garantiza que el segundo hilo esté informado sobre un cambio de una variable.

Por lo tanto, tiene que marcar ambos métodos como synchronized o marcar el miembro myVar como volatile.

Hay una muy buena explicación acerca de la sincronización here:

acciones atómicas no pueden ser intercalados, por lo que se puede utilizar sin temor a la interferencia de rosca. Sin embargo, esto no elimina toda necesidad de sincronizar acciones atómicas, porque los errores de consistencia de la memoria aún son posibles. El uso de variables volátiles reduce el riesgo de errores de consistencia de la memoria, ya que cualquier escritura en una variable volátil establece una relación de pasar antes a las lecturas posteriores de esa misma variable. Esto significa que los cambios en una variable volátil siempre son visibles para otros hilos. Lo que es más, también significa que cuando un hilo lee una variable volátil, no solo ve el último cambio en el volátil, sino también los efectos secundarios del código que provocó el cambio.

0

El captador no es seguro para subprocesos, ya que no está protegido por ningún mecanismo que garantice la visibilidad más actualizada. Las opciones son:

  • hacer myVar final (pero entonces no se puede mutar ella)
  • myVar haciendo uso volátil
  • sincronizado con el acceso a myVar
0

yo sepa, Moderno JVM ya no se divide operaciones largas y dobles. No sé de ninguna referencia que indique que esto todavía es un problema. Por ejemplo, vea AtomicLong que no usa sincronización en la JVM de Sun.

Suponiendo que quiere estar seguro de que no es un problema, puede usar la sincronización de get() y set(). Sin embargo, si está realizando una operación como agregar, es decir, establecer (obtener() + 1), esta sincronización no le cuesta mucho, aún tiene que sincronizar el objeto para toda la operación. (Una mejor forma de evitar esto es usar una sola operación para agregar (n) que esté sincronizado)

Sin embargo, una mejor solución es usar un AtomicLong. Esto es compatible con operaciones atómicas como get, set y add, y NO utiliza la sincronización.