¿Método sincronizado de Java bloqueado en objeto o método?

191

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

Ejemplo:

class X {

    private int a;
    private int b;

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

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

}

¿Pueden 2 hilos acceder a la misma instancia de rendimiento de clase X x.addA() y x.addB()al mismo tiempo?

wuntee
fuente

Respuestas:

197

Si declaras que el método está sincronizado (como lo haces escribiendo public synchronized void addA()), sincronizas todo el objeto, por lo que dos hilos que acceden a una variable diferente de este mismo objeto se bloquearían de todos modos.

Si desea sincronizar solo en una variable a la vez, para que dos hilos no se bloqueen entre sí al acceder a diferentes variables, debe sincronizarlos por separado en synchronized ()bloques. Si ay bfueran referencias de objetos que usaría:

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

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

Pero como son primitivos, no puedes hacer esto.

Te sugiero que uses AtomicInteger en su lugar:

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

    public void addA(){
        a.incrementAndGet();
    }

    public void addB(){ 
        b.incrementAndGet();
    }
}
OscarRyz
fuente
181
Si sincroniza en el método, bloquea todo el objeto, por lo que dos hilos que acceden 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 synchronized (this)bloque 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.
Mark Peters
13
Sí, es realmente engañoso. Por ejemplo real - Mire esto - stackoverflow.com/questions/14447095/… - Resumen: El bloqueo es solo a nivel de método sincronizado y las variables de instancia del objeto pueden ser accedidas por otro hilo
mac
55
El primer ejemplo está fundamentalmente roto. Si ay bfueran objetos, por ejemplo, Integers, estaba sincronizando en instancias que está reemplazando con diferentes objetos al aplicar el ++operador.
Holger
corrija su respuesta e inicialice AtomicInteger: AtomicInteger a = new AtomicInteger (0);
Mehdi
Quizás esta respuesta debería actualizarse con la explicada en esta otra sobre la sincronización en el objeto mismo: stackoverflow.com/a/10324280/1099452
lucasvc
71

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

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

En un método estático, es azúcar sintáctico para esto:

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

Creo que si los diseñadores de Java supieran lo que se entiende ahora sobre sincronización, no habrían agregado el azúcar sintáctico, ya que la mayoría de las veces conduce a malas implementaciones de concurrencia.

Yishai
fuente
3
No es verdad. El método sincronizado genera un código de bytes diferente al sincronizado (objeto). Si bien la funcionalidad es equivalente, es más que solo azúcar sintáctico.
Steve Kuo
10
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 monitores, lo habrían / ​​deberían haber hecho de manera diferente, en lugar de emular básicamente las entrañas de Unix. Per Brinch Hansen dijo 'claramente he trabajado en vano' cuando vio las primitivas de concurrencia de Java .
Marqués de Lorne
Esto es verdad. El ejemplo dado por OP parecería bloquear cada método, pero de hecho todos se bloquean en el mismo objeto. Sintaxis muy engañosa. Después de usar Java durante más de 10 años, no lo sabía. Por lo tanto, evitaría métodos sincronizados por este motivo. Siempre pensé que se creó un objeto invisible para cada método que se definió con sincronizado.
Peter Quiring el
21

De "Los Tutoriales Java ™" sobre métodos sincronizados :

Primero, no es posible intercalar dos invocaciones de métodos sincronizados en el mismo objeto . Cuando un subproceso está ejecutando un método sincronizado para un objeto, todos los demás subprocesos que invocan métodos sincronizados para el mismo bloque de objeto (suspender la ejecución) hasta que el primer subproceso se realiza con el objeto.

De "The Java ™ Tutorials" en bloques sincronizados :

Las declaraciones sincronizadas también son útiles para mejorar la concurrencia con la sincronización fina. 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 ninguna razón para evitar que una actualización de c1 se intercala con una actualización de c2 , y al hacerlo, se reduce la concurrencia creando bloqueos innecesarios. En lugar de usar métodos sincronizados o de otro modo usar el bloqueo asociado con esto, creamos dos objetos únicamente para proporcionar bloqueos.

(El énfasis es mío)

Supongamos que tiene 2 variables no intercaladas . Por lo tanto, desea acceder a cada uno desde diferentes hilos al mismo tiempo. Debe definir el bloqueo no en la clase de objeto en sí, sino en la clase Objeto como se muestra a continuación (ejemplo del 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++;
        }
    }
}
MehdiMAH
fuente
14

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 subproceso que ejecuta el código debe adquirir el bloqueo en el objeto antes de continuar. Agregar "estática sincronizada" significa que el hilo que ejecuta el código debe adquirir el bloqueo en el objeto de clase antes de continuar. Alternativamente, puede envolver el código en un bloque como este:

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

para que pueda especificar el objeto cuyo bloqueo debe ser adquirido.

Si desea evitar bloquear el objeto que lo contiene, puede elegir entre:

Nathan Hughes
fuente
7

De la documentación de Oracle enlace de Oracle

Hacer métodos sincronizados tiene dos efectos:

Primero, no es posible intercalar dos invocaciones de métodos sincronizados en el mismo objeto. Cuando un subproceso está ejecutando un método sincronizado para un objeto, todos los demás subprocesos que invocan métodos sincronizados para el mismo bloque de objeto (suspender la ejecución) hasta que el primer subproceso se realiza con el objeto.

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

Eche un vistazo a esta página de documentación para comprender los bloqueos intrínsecos y el comportamiento del 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.

Aditya W
fuente
4

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

 private int a;
 private int b;

cualquier cantidad de subprocesos puede acceder a estos métodos no sincronizados al mismo tiempo cuando otro subproceso está en el método sincronizado del mismo objeto y puede realizar cambios en las variables de instancia. Por ejemplo: -

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

Debe evitar el escenario de que los métodos no sincronizados están accediendo a las variables de instancia y cambiándolas; de lo contrario, no tiene sentido utilizar métodos sincronizados.

En el siguiente escenario: -

class X {

        private int a;
        private int b;

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

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

Solo uno de los hilos puede estar en el método addA o addB, pero al mismo tiempo, cualquier número de hilos puede ingresar al método changeState. No hay dos subprocesos que puedan ingresar addA y addB al mismo tiempo (debido al bloqueo de nivel de objeto), pero al mismo tiempo cualquier cantidad de subprocesos puede ingresar changeState.

Goyal Vicky
fuente
3

Puedes 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 usamos Integer.

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

Sí, bloqueará el otro método porque el método sincronizado se aplica al objeto de clase WHOLE como se señala ... pero de todos modos bloqueará la ejecución del otro hilo SOLAMENTE mientras realiza la suma en cualquier método addA o addB que ingrese, porque cuando termine ... el hilo se LIBERAR el objeto y el otro hilo accederá a otro método y así sucesivamente perfecto estado de funcionamiento.

Quiero decir que el "sincronizado" está hecho precisamente para bloquear el acceso del otro hilo a otro mientras se ejecuta un código específico. FINALMENTE ESTE CÓDIGO FUNCIONARÁ BIEN.

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

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

Funcionará tan bien

Jose Velandia
fuente
2

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

Ambos métodos se invocan en una sola instancia - objeto, en este ejemplo es: trabajo , y los hilos 'competidores' son aThread y main .

Pruebe con ' sincronizado ' en el incremento B y sin él y verá resultados diferentes. Si el incremento B también está ' sincronizado ', entonces tendrá que esperar a que termine el incremento A (). 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();
    }
}
Nenad Bulatovic
fuente
1

En la sincronización de Java, si un subproceso desea ingresar al método de sincronización, se bloqueará en todos los métodos sincronizados de ese objeto, no solo en un método sincronizado que esté usando ese subproceso. Por lo tanto, un subproceso que ejecuta addA () adquirirá el bloqueo en addA () y addB () ya que ambos están sincronizados, por lo que otros subprocesos con el mismo objeto no pueden ejecutar addB ().

Sreedhar Reddy
fuente
0

Esto podría no funcionar ya que el boxeo y el autoboxing de Integer a int y viceversa dependen de JVM y existe una alta posibilidad de que dos números diferentes se mezclen en la misma dirección si están entre -128 y 127.

Sriharsha grv
fuente