Java método sincronizado bloquear objeto, o método?


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? ¿Ocurre 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 subprocesos acceder a la misma instancia de la clase X realizando x.addA() y x.addB() al mismo tiempo?

Author: informatik01, 2010-06-15

10 answers

Si declaras el método como sincronizado (como lo estás haciendo al escribir public synchronized void addA()) te sincronizas en el objeto completo, por lo que dos subprocesos que acceden a una variable diferente de este mismo objeto se bloquearían entre sí de todos modos.

Si desea sincronizar solo en una variable a la vez, para que dos subprocesos no se bloqueen entre sí mientras accede a diferentes variables, debe sincronizar en ellos por separado en synchronized () bloques. Si a y b fueran referencias de objetos, uso:

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

Pero como son primitivos no puedes hacer esto.

Le sugeriría que use 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();
    }
}
 152
Author: OscarRyz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-17 18:14:31

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

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

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

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

Creo que si los diseñadores de Java supieran entonces lo que se entiende ahora sobre la 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.

 50
Author: Yishai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-27 10:18:03

El bloqueo al que se accede está en el objeto, no en el método. Las variables a las que se accede dentro del método son irrelevantes.

Agregar "sincronizado" al método significa que el hilo que ejecuta el código debe adquirir el bloqueo en el objeto antes de continuar. Agregar "sincronizado estático" 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 cuya lock debe ser adquirido.

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

 12
Author: Nathan Hughes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:36

De Java SE essentials 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 (suspenden la ejecución) hasta que el primer subproceso termine con el objeto.

Desde el Java SE essentials en sincronizado bloques:

Las sentencias sincronizadas 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 sincronizarse, pero no hay razón para evitar que una actualización de c1 se intercale con una actualización de c2, y hacerlo reduce la concurrencia al crear un bloqueo innecesario. En lugar de utilizar sincronizado métodos o de otra manera usando el bloqueo asociado con esto, creamos dos objetos únicamente para proporcionar bloqueos.

(Énfasis mío.)

Tiene 2 variables sin interleaved. Por lo tanto, desea acceder a cada uno desde diferentes hilos al mismo tiempo. necesita definir el bloqueo no en la clase del objeto en sí, sino en el Objeto de la clase como se muestra a continuación (ejemplo del segundo Oracle link):

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++;
        }
    }
}
 9
Author: MehdiMAH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-12 07:38:31

De la documentación de oracle link

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 (suspenden la ejecución) hasta que el primer subproceso termine con el objeto.

Segundo, cuando un método sincronizado sale, establece automáticamente una relación happens-before 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 de los bloqueos.

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 la ejecución está en curso.

 5
Author: Aditya W,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-21 10:12:15

Puedes hacer algo como lo siguiente. En este caso, está utilizando el bloqueo en a y b 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++;
      }
   }
}
 2
Author: dsmith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-15 18:05:10

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 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 accedan a las variables de instancia y las cambien 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 subprocesos puede estar en el método addA o addB, pero al mismo tiempo cualquier número de subprocesos puede ingresar al método changeState. No hay dos subprocesos que puedan ingresar addA y addB al mismo tiempo (debido al bloqueo a nivel de objeto), pero al mismo tiempo cualquier número de subprocesos puede ingresar a changeState.

 2
Author: Goyal Vicky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-07 11:09:36

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

Ambos métodos se llaman a single instance-object, en este ejemplo se es: job, y los hilos 'competidores' son aThread y main.

Intente con ' sincronizado' en incrementB y sin él y verá resultados diferentes.Si incrementBes ' sincronizado' entonces tiene que esperar a que Increenta () termine. Ejecutar 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();
    }
}
 1
Author: Nenad Bulatovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-09 16:52:17

Sí, bloqueará el otro método porque el método sincronizado se aplica al objeto de clase WHOLE como apuntado .... pero de todos modos bloqueará la ejecución del otro subproceso SOLO mientras realiza la suma en cualquier método addA o addB que ingrese, porque cuando termine ... un hilo LIBERARÁ 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 hilo de acceder a otro mientras está en una ejecución de código específico. ASÍ QUE FINALMENTE ESTE CÓDIGO FUNCIONARÁ BIEN.

Como nota final, si hay variables '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 otra var (Otra ubicación de memoria).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

Funcionará también

 1
Author: Jose Velandia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-26 22:29:23

Esto podría no funcionar ya que el boxing y autoboxing de Integer a int y viceversa depende de JVM y hay una alta posibilidad de que dos números diferentes puedan obtener hash a la misma dirección si están entre -128 y 127.

 0
Author: Sriharsha g.r.v,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-29 21:49:55