2010-06-15 15 views
131

Si tengo 2 métodos sincronizados en la misma clase, pero cada uno accediendo a diferentes variables, ¿pueden 2 subprocesos acceder a esos 2 métodos al mismo tiempo? ¿Se produce el bloqueo en el objeto, o es tan específico como las variables dentro del método sincronizado?¿Bloqueo de método sincronizado de Java en objeto o método?

Ejemplo:

class X { 

    private int a; 
    private int b; 

    public synchronized void addA(){ 
     a++; 
    } 

    public synchronized void addB(){ 
     b++; 
    } 

} 

Can 2 hilos de acceso a la misma instancia de la clase X realizar x.addA() y x.addB() al mismo tiempo?

Respuesta

137

Si se declara el método sincronizado (como se está haciendo escribiendo public synchronized void addA()) sincroniza en general objeto , por lo que dos hilos para acceder a una variable diferente de este mismo objeto bloquearía entre sí de todos modos.

Si desea sincronizar solo en una variable a la vez, por lo que dos hilos no se bloquearán entre sí mientras acceden a diferentes variables, se sincronizarán separadamente en los bloques synchronized(). Si a y b eran referencias a objetos se debería utilizar:

public void addA() { 
    synchronized(a) { 
     a++; 
    } 
} 
public void addB() { 
    synchronized(b) { 
     b++; 
    } 
} 

Pero ya que son primitivas no se puede hacer esto.

Yo sugeriría que usted utilice AtomicInteger lugar:

import java.util.concurrent.atomic.AtomicInteger; 
class X { 
    AtomicInteger a; 
    AtomicInteger b; 
    public void addA(){ 
     a.incrementAndGet(); 
    } 
    public void addB(){ 
     b.incrementAndGet(); 
    } 
} 
+121

* Si sincroniza el método, bloquea el objeto completo, por lo que dos subprocesos que accedan a una variable diferente de este mismo objeto se bloquearían entre sí de todos modos. * Eso es un poco engañoso. La sincronización en el método es funcionalmente equivalente a tener un bloque 'synchronized (this)' alrededor del cuerpo del método. El objeto "this" no se bloquea, sino que el objeto "this" se usa como mutex y se impide que el cuerpo se ejecute simultáneamente con otras secciones de código también sincronizadas en "this". No tiene ningún efecto en otros campos/métodos de "esto" que no están sincronizados. –

+9

Sí, es realmente engañoso. Para un ejemplo real - Mira esto - http://stackoverflow.com/questions/14447095/does-java-monitor-include-instance-variables - Resumen: el bloqueo está solo en el nivel de método sincronizado y las variables de instancia del objeto pueden ser accedidas por otro thread – mac

+3

El primer ejemplo está fundamentalmente roto. Si 'a' y' b' son objetos, p. 'Entero's, estabas sincronizando en las instancias * reemplazando con diferentes objetos * cuando aplicabas el operador' ++ '. – Holger

12

El bloqueo al que se accede está en el objeto, no en el método. A qué variables se accede dentro del método es irrelevante.

Agregar "sincronizado" al método significa que el hilo que ejecuta el código debe adquirir el bloqueo del objeto antes de continuar. Agregar "estático sincronizado" significa que el hilo que ejecuta el código debe adquirir el bloqueo en el objeto de la clase antes de continuar. Como alternativa se puede envolver el código en un bloque de la siguiente manera:

public void addA() { 
    synchronized(this) { 
     a++; 
    } 
} 

de manera que se puede especificar el objeto cuya cerradura debe ser adquirido.

Si desea evitar el bloqueo del objeto que contiene se puede elegir entre:

44

Sincronizado en la declaración del método es azúcar sintáctico para esto:

public void addA() { 
    synchronized (this) { 
      a++; 
    } 
    } 

En un método estático que es el azúcar sintáctica para esto:

ClassA { 
    public static void addA() { 
      synchronized(ClassA.class) { 
       a++; 
      } 
} 

Creo que si los diseñadores de Java sabido entonces lo que se entiende ahora acerca de la sincronización, no habrían añadido el azúcar sintáctica, ya que las más de las veces conduce a malas implementaciones de concurrencia.

+3

No es cierto. el método sincronizado genera diferentes bytecode que sincronizados (objeto). Mientras que la funcionalidad es equivalente, es más que solo azúcar sintáctica. –

+9

No creo que el "azúcar sintáctico" se defina estrictamente como equivalente de código de bytes. El punto es que es funcionalmente equivalente. – Yishai

+1

Si los diseñadores de Java hubieran sabido lo que * ya * se sabía sobre los monitores, lo habrían/​​deberían haber hecho de manera diferente, en lugar de básicamente emular las entrañas de Unix. [Per Brinch Hansen dijo 'claramente he trabajado en vano' cuando vio las primitivas de simultaneidad Java] (https://forums.oracle.com/thread/1142765?start=0&tstart=342). – EJP

2

se puede hacer algo como lo siguiente. En este caso, está utilizando el bloqueo en ayb para sincronizar en lugar del bloqueo en "esto".No podemos usar int porque los valores primitivos no tienen bloqueos, por lo que utilizamos Integer.

class x{ 
    private Integer a; 
    private Integer b; 
    public void addA(){ 
     synchronized(a) { 
     a++; 
     } 
    } 
    public synchronized void addB(){ 
     synchronized(b) { 
     b++; 
     } 
    } 
} 
0

Esto podría no funcionar como el boxeo y autoboxing desde un entero a int y viceversa depende de la JVM y existe una alta posibilidad de que dos números diferentes podrían obtener hash a la misma dirección si están entre -128 y 127.

2

Si tiene algunos métodos que no están sincronizados y están accediendo y cambiando las variables de instancia. En su ejemplo:

private int a; 
private int b; 

cualquier número de hilos pueden tener acceso a estos métodos no sincronizadas al mismo tiempo cuando otro hilo está en el método sincronizado del mismo objeto y se puede hacer cambios en las variables de instancia. Para por ejemplo: -

public void changeState() { 
     a++; 
     b++; 
    } 

Es necesario evitar el escenario que los métodos no sincronizados están accediendo a las variables de instancia y cambiar de otro modo no hay ningún punto de la utilización de métodos sincronizados.

En el escenario a continuación: -

class X { 

     private int a; 
     private int b; 

     public synchronized void addA(){ 
      a++; 
     } 

     public synchronized void addB(){ 
      b++; 
     } 
    public void changeState() { 
      a++; 
      b++; 
     } 
    } 

Sólo uno de los hilos puede ser o bien en el método ADDA o Addb pero al mismo tiempo cualquier número de hilos puede entrar método ChangeState. No hay dos subprocesos que puedan entrar en addA y addB al mismo tiempo (debido al bloqueo de nivel de objeto) pero al mismo tiempo cualquier cantidad de subprocesos puede entrar en changeState.

5

De la documentación de Oracle link

métodos Haciendo sincronizados tiene dos efectos:

primer lugar, no es posible que dos invocaciones de métodos sincronizados en el mismo objeto para intercalar. Cuando un hilo está ejecutando un método sincronizado para un objeto, todos los otros hilos que invocan métodos sincronizados para el mismo bloque de objetos (suspenden la ejecución) hasta que el primer hilo termina con el objeto.

En segundo lugar, cuando sale un método sincronizado, establece automáticamente una relación de pasar antes con cualquier invocación posterior de un método sincronizado para el mismo objeto. Esto garantiza que los cambios en el estado del objeto son visibles para todos los hilos

echar un vistazo a esta documentación page entender cerraduras intrínsecas y el comportamiento de bloqueo.

Esto responderá a su pregunta: En el mismo objeto x, no puede llamar a x.addA() y x.addB() al mismo tiempo cuando uno de los métodos sincronizados está en ejecución.

1

Este ejemplo (aunque no es bonito) puede proporcionar más información sobre el mecanismo de bloqueo.Si Incrementa es sincronizada, y incrementB es no sincronizados, entonces incrementB se ejecutará lo antes posible, pero si incrementB es también sincronizada entonces tiene que 'esperar' para Incrementa a terminar, antes de incrementB puede hacer su trabajo.

Ambos métodos se llaman en única instancia - objeto, en este ejemplo, es: trabajo, y los hilos 'compiten' son aThread y principal.

Pruebe con 'sincronizados' en incrementB y sin ella y verá diferente results.If incrementB es 'sincronizados' así, entonces tiene que esperar a que Incrementa() para terminar . Ejecute varias veces cada variante.

class LockTest implements Runnable { 
    int a = 0; 
    int b = 0; 

    public synchronized void incrementA() { 
     for (int i = 0; i < 100; i++) { 
      this.a++; 
      System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a); 
     } 
    } 

    // Try with 'synchronized' and without it and you will see different results 
    // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish 

    // public void incrementB() { 
    public synchronized void incrementB() { 
     this.b++; 
     System.out.println("*************** incrementB ********************"); 
     System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b); 
     System.out.println("*************** incrementB ********************"); 
    } 

    @Override 
    public void run() { 
     incrementA(); 
     System.out.println("************ incrementA completed *************"); 
    } 
} 

class LockTestMain { 
    public static void main(String[] args) throws InterruptedException { 
     LockTest job = new LockTest(); 
     Thread aThread = new Thread(job); 
     aThread.setName("aThread"); 
     aThread.start(); 
     Thread.sleep(1); 
     System.out.println("*************** 'main' calling metod: incrementB **********************"); 
     job.incrementB(); 
    } 
} 
6

From the Java SE essentials on synchronized methods:

primer lugar, no es posible que dos invocaciones de métodos sincronizados en el mismo objeto para intercalar. Cuando un hilo está ejecutando un método sincronizado para un objeto, todos los otros hilos que invocan métodos sincronizados para el mismo bloque de objetos (suspenden la ejecución) hasta que el primer hilo termina con el objeto.

Desde el Java SE essentials on synchronized blocks:

declaraciones sincronizados también son útiles para mejorar la concurrencia con la sincronización de grano fino. Supongamos, por ejemplo, que la clase MsLunch tiene dos campos de instancia, c1 y c2, que nunca se usan juntos. Todas las actualizaciones de estos campos deben estar sincronizadas, , pero no hay razón para evitar que una actualización de c1 se intercalé con una actualización de c2, y al hacerlo se reduce la concurrencia al crear bloqueos innecesarios. En lugar de utilizar métodos sincronizados o utilizar el bloqueo asociado a esto, creamos dos objetos únicamente para proporcionar bloqueos.

(El subrayado es mío.)

Usted tiene 2 variables no-entrelazados. Entonces quiere acceder a cada uno desde diferentes hilos al mismo tiempo. es necesario definir la cerradura no en la misma clase de objeto, pero en la clase Object, como a continuación (ejemplo desde el segundo enlace de Oracle):

public class MsLunch { 

    private long c1 = 0; 
    private long c2 = 0; 
    private Object lock1 = new Object(); 
    private Object lock2 = new Object(); 

    public void inc1() { 
     synchronized(lock1) { 
      c1++; 
     } 
    } 

    public void inc2() { 
     synchronized(lock2) { 
      c2++; 
     } 
    } 
} 
1

Sí, bloqueará el otro método, ya método sincronizado se aplica a la TODO objeto de clase como apuntado ... pero de todos modos bloqueará la ejecución del otro hilo SOLAMENTE mientras realiza la suma en cualquier método addA o addB que ingresa, porque cuando termine ...el único hilo será FREE el objeto y el otro hilo accederá al otro método y así sucesivamente funcionando perfectamente.

Quiero decir que el "sincronizado" se hace precisamente para bloquear el otro subproceso de acceder a otro mientras se está ejecutando un código específico. ASÍ QUE FINALMENTE ESTE CÓDIGO FUNCIONARÁ BIEN.

Como nota final, si hay una 'a' y 'b' variables, no solo una variable única 'a' o cualquier otro nombre, no hay necesidad de sincronizar estos métodos porque es perfectamente seguro acceder a otros var (Otra ubicación de memoria).

class X { 

private int a; 
private int b; 

public void addA(){ 
    a++; 
} 

public void addB(){ 
    b++; 
}} 

funcionará tan bien

Cuestiones relacionadas