¿Bloqueo de método sincronizado de Java en objeto o método?

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 locking en el objeto, o es 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 clase X ejecutando x.addA( ) y x.addB() al mismo tiempo?

Si declara que el método está sincronizado (como está haciendo al escribir public synchronized void addA() ) se sincroniza en todo el objeto, por lo que dos subprocesos que accedan 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, de modo que dos hilos no se bloquearán entre sí mientras acceden a diferentes variables, usted se sincronizará separadamente en bloques synchronized () . Si a y b fueran referencias de objeto usarías:

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

Pero dado que son primitivos, no puedes hacer esto.

Le sugiero 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(); } } 

Sincronizado en la statement del método hay azúcar sintáctica 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 entonces qué se entiende ahora acerca de la sincronización, no habrían agregado el azúcar sintáctico, ya que a menudo conduce a malas implementaciones de concurrencia.

El locking 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 locking del objeto antes de continuar. Agregar “estático sincronizado” significa que el hilo que ejecuta el código debe adquirir el locking en el objeto de la clase antes de continuar. Alternativamente, puede ajustar el código en un bloque como este:

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

para que pueda especificar el objeto cuyo locking debe adquirirse.

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

  • usando bloques sincronizados que especifican diferentes lockings
  • haciendo a y b atomic (usando java.util.concurrent.atomic)

Desde el Java SE essentials en métodos sincronizados :

En primer lugar, no es posible intercalar dos invocaciones de métodos sincronizados en el mismo objeto . 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 los elementos esenciales de Java SE en bloques sincronizados :

Las declaraciones sincronizadas también son útiles para mejorar la concurrencia con una 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 ninguna razón para evitar que una actualización de c1 se intercalde con una actualización de c2 , y al hacerlo se reduce la concurrencia al crear lockings innecesarios. En lugar de utilizar métodos sincronizados o utilizar el locking asociado a esto, creamos dos objetos únicamente para proporcionar lockings.

(Énfasis mío)

Tienes 2 variables sin entrelazado. Entonces quiere acceder a cada uno desde diferentes hilos al mismo tiempo. es necesario definir el locking no en la clase de objeto en sí, sino en la clase Objeto como 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++; } } } 

Desde el enlace de documentación de Oracle

Hacer que los métodos estén sincronizados tiene dos efectos:

En primer lugar, no es posible intercalar dos invocaciones de métodos sincronizados en el mismo objeto. 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 sean visibles para todos los hilos

Eche un vistazo a esta página de documentación para comprender los lockings intrínsecos y el comportamiento de locking.

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.

Puedes hacer algo como lo siguiente. En este caso, está utilizando el locking en ayb para sincronizar en lugar del locking en “esto”. No podemos usar int porque los valores primitivos no tienen lockings, 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++; } } } 

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 la posibilidad de que los métodos no sincronizados accedan a las variables de instancia y la modifiquen; 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 cantidad de subprocesos puede ingresar al método changeState. No hay dos subprocesos que puedan entrar en addA y addB al mismo tiempo (debido al locking de nivel de objeto) pero al mismo tiempo cualquier cantidad de subprocesos puede entrar en changeState.

Este ejemplo (aunque no es bonito) puede proporcionar más información sobre el mecanismo de locking. Si incrementA está sincronizado , y incrementB no está sincronizado , entonces incrementB se ejecutará lo antes posible, pero si incrementB también está sincronizado, entonces tiene que ‘esperar’ para que incrementeA termine, antes de que incrementB pueda hacer su trabajo.

Ambos métodos se invocan a instancia única: objeto, en este ejemplo es: trabajo , y los hilos “competidores” son a Tema y principal .

Pruebe con ‘ sincronizado ‘ en incrementB y sin él y verá resultados diferentes. Si incrementB también está ‘ sincronizado ‘, tendrá que esperar que incrementeA () termine. 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(); } } 

Sí, bloqueará el otro método porque el método sincronizado se aplica al objeto de clase WHOLE como apuntado … pero bloqueará la ejecución del otro hilo SÓLO mientras realiza la sum en cualquier método addA o addB que ingrese, porque cuando termine … el único hilo liberará el objeto y el otro hilo accederá al otro método y así sucesivamente funcionando perfectamente.

Me refiero a que el “sincronizado” está hecho 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 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 otras var (Otra memoria ubicación).

 class X { private int a; private int b; public void addA(){ a++; } public void addB(){ b++; }} 

Funcionará bien

Es posible que esto no funcione ya que el boxeo y el autoboxing de Integer a int y viceversa dependen de JVM y hay una gran posibilidad de que dos números diferentes puedan obtener hash en la misma dirección si están entre -128 y 127.