2008-11-21 12 views
18

¿En qué casos es necesario sincronizar el acceso a los miembros de la instancia? Entiendo que el acceso a los miembros estáticos de una clase siempre debe sincronizarse, ya que se comparten en todas las instancias de objeto de la clase.¿Qué casos requieren acceso al método sincronizado en Java?

Mi pregunta es cuándo sería incorrecto si no sincronizo los miembros de la instancia?

por ejemplo, si mi clase es

public class MyClass { 
    private int instanceVar = 0; 

    public setInstanceVar() 
    { 
     instanceVar++; 
    } 

    public getInstanceVar() 
    { 
     return instanceVar; 
    } 
} 

en qué casos (del uso de la clase MyClass) iba a necesitar tener métodos: public synchronized setInstanceVar() y public synchronized getInstanceVar()?

Gracias de antemano por sus respuestas.

Respuesta

19

Depende de si desea que su clase sea segura para subprocesos. La mayoría de las clases no deben ser seguras para subprocesos (por simplicidad) en cuyo caso no necesita sincronización. Si necesita que sea seguro para subprocesos, debe sincronizar el acceso o para hacer que la variable sea volátil. (Evita que otros hilos obtengan datos "obsoletos")

-3

En Java, las operaciones en ints son atómicas, por lo que no, en este caso no necesita sincronizar si todo lo que hace es 1 escritura y 1 lectura en un momento.

Si se trata de largos o dobles, debe sincronizar porque es posible actualizar parte del largo/doble, luego leer otro hilo, y finalmente la otra parte de la larga/doble actualizada.

+1

Aunque tenga cuidado con los operadores ++ y -, no son atómicos. Para estar seguro, intente usar clases de java.util.concurrent.atomic. – InverseFalcon

+0

Las operaciones en enteros no son atómicas en Java. –

+0

Puede modificar un int de diferentes subprocesos, obtener el almacenamiento en caché localizado en los subprocesos, tener la operación de lectura y escritura de sincronización de sincronización. El hecho de que los largos y los dobles lean y escriban como dos operaciones op (carga superior y carga más baja para obtener los 64 bits) no significa que los enteros se vuelvan súbitamente atómicos porque sufren de 1 menos problemas que no impiden la amenaza de los hilos. – Tatarize

3

Si usted quiere hacer este hilo clase segura yo anunciare instanceVar como volatile para asegurarse de obtener siempre el valor más actualizada de la memoria y también me gustaría hacer el setInstanceVar()synchronized porque en la JVM un incremento no es una operación atómica .

private volatile int instanceVar =0; 

public synchronized setInstanceVar() { instanceVar++; 

} 
+3

No es necesario que el campo sea volátil, pero setInstanceVar() no necesita sincronizarse. –

+1

O puede usar un java.util.concurrent.atomic.AtomicInteger. – InverseFalcon

+1

Daniel: Si el getInstanceVar no está sincronizado, el volátil se asegura de que el valor devuelto sea nuevo.Pero eso probablemente se perderá en la mayoría de los programadores. –

1

. Aproximadamente, la respuesta es "depende". La sincronización de su setter y getter aquí sólo tendría la finalidad de garantizar que múltiples hilos no podían leer las variables entre cada uno de los demás incrementar las operaciones de:

synchronized increment() 
{ 
     i++ 
} 

synchronized get() 
{ 
    return i; 
    } 

pero eso no sería realmente incluso trabajar aquí, porque para asegurar que su hilo de llamadas consiguió el mismo valor que incrementa, se tendría que garantizar que se está incrementando de forma atómica y luego recuperar, lo que no estás haciendo aquí - es decir, que tendría que hacer algo como

synchronized int { 
    increment 
    return get() 
    } 

Básicamente , la sincronización es útil para definir qué operaciones deben garantizarse para ejecutar threadsafe (inotherword s, no puede crear una situación en la que un hilo separado socave su operación y haga que su clase se comporte de forma ilógica, o socave lo que espera que sea el estado de los datos). En realidad es un tema más grande que se puede abordar aquí.

Este libro Java Concurrency in Practice es excelente, y ciertamente mucho más fiable que yo.

35

El modificador synchronized es realmente una idea mala y debe evitarse a toda costa.Creo que es encomiable que Sun haya tratado de hacer que el bloqueo sea un poco más fácil de lograr, pero synchronized solo causa más problemas de los que merece la pena.

El problema es que un método synchronized es en realidad un azucar de sintaxis para obtener el bloqueo en this y mantenerlo durante todo el proceso. Por lo tanto, public synchronized void setInstanceVar() sería equivalente a algo como esto:

public void setInstanceVar() { 
    synchronized(this) { 
     instanceVar++; 
    } 
} 

Esto es malo por dos razones:

  • Todos synchronized métodos dentro de la misma clase de uso de la misma cerradura, que el rendimiento se reduce
  • Cualquiera puede obtener acceso a la cerradura, incluidos los miembros de otras clases.

No hay nada que me impida hacer algo como esto en otra clase:

MyClass c = new MyClass(); 
synchronized(c) { 
    ... 
} 

Dentro de ese bloque synchronized, estoy sosteniendo la cerradura que es requerido por todos los métodos synchronized dentro MyClass. Esto reduce aún más el rendimiento y dramáticamente aumenta las posibilidades de un punto muerto.

Un mejor enfoque es tener un lock objeto dedicado y utilizar el bloque synchronized(...) directamente:

public class MyClass { 
    private int instanceVar; 
    private final Object lock = new Object();  // must be final! 

    public void setInstanceVar() { 
     synchronized(lock) { 
      instanceVar++; 
     } 
    } 
} 

Como alternativa, puede utilizar la interfaz java.util.concurrent.Lock y la implementación java.util.concurrent.locks.ReentrantLock para lograr básicamente el mismo resultado (de hecho , es lo mismo en Java 6).

+1

@Daniel Me gustó mucho su explicación, pero ¿no debería 'declararse' estático ', especialmente para tener clases públicas seguras de subprocesos? Como también desea garantizar que todos los hilos para lograr el bloqueo usen el mismo objeto (compartido) que no se modificará. 'private static final Object lock = new Object(); // debe ser estático final' – sactiw

+2

No es necesario que 'lock' sea' static' ya que los datos que está sincronizando ('instanceVar') no son estáticos. Los bloqueos estáticos son casi siempre un signo de problemas muy, muy graves y debe evitarlos siempre que sea posible. –

+0

@Daniel hmmm ... Supongo que olvidé el punto de que aquí 'MyClass' no extiende Thread para que no tenga sus propios hilos. Así que aquí el propósito de hacer que el hilo de la clase sea seguro es que cuando algunos hilos externos comparten el mismo objeto (instancia) de 'MyClass', entonces deben acceder [update] al instanceVar de forma sincronizada. En otras palabras, la cerradura debería haber sido declarada como "estática" si MyClass fue declarada extendiendo el hilo. ¿Derecha? – sactiw

1

En pocas palabras, se usa sincronizado cuando se tienen subprocesos múltiples que acceden al mismo método de la misma instancia que cambiará el estado del objeto/aplicación.

Se entiende como una forma simple de evitar condiciones de carrera entre subprocesos, y realmente solo debe usarlo cuando esté planeando tener subprocesos simultáneos que accedan a la misma instancia, como un objeto global.

Ahora cuando está leyendo el estado de una instancia de un objeto con subprocesos concurrentes, es posible que desee buscar en el java.util.concurrent.locks.ReentrantReadWriteLock - que en teoría permite que muchos subprocesos lean en un tiempo, pero solo se permite escribir un hilo. Por lo tanto, en el método getter y de configuración que todo el mundo parece estar dando, puede hacer lo siguiente:

public class MyClass{ 
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
    private int myValue = 0; 

    public void setValue(){ 
     rwl.writeLock().lock(); 
     myValue++; 
     rwl.writeLock().unlock(); 
    } 

    public int getValue(){ 
     rwl.readLock.lock(); 
     int result = myValue; 
     rwl.readLock.unlock(); 
     return result; 
    } 
} 
+0

El problema con ReadWriteLock es que impone una sobrecarga adicional. Esta sobrecarga solo vale la pena si tienes operaciones de lectura * long *, de lo contrario estarás mejor con solo un ReentrantLock. –

Cuestiones relacionadas