Saltearse al contenido

Métodos y bloques sincronizados

Bloques sincronizados en métodos de instancia

No es necesario que sincronicemos un método completo. A veces es preferible sincronizar solo una parte de uno. Los bloques sincronizados de Java dentro de los métodos hacen esto posible.

El siguiente es un bloque de código Java sincronizado dentro de un método de Java no sincronizado:

1
public void sumar(int valor) {
2
synchronized(this) {
3
this.cuenta += valor;
4
}
5
}

Este ejemplo utiliza la construcción de bloque sincronizado de Java para marcar un bloque de código como sincronizado. Este código ahora se ejecutará como si fuera un método sincronizado.

Observemos cómo la construcción del bloque sincronizado de Java toma un objeto entre paréntesis. En el ejemplo se usa “this”, que es la instancia en la que se llama al método sumar. El objeto tomado entre paréntesis por la construcción sincronizada se llama objeto monitor. Se dice que el código está sincronizado en el objeto monitor. Un método de instancia sincronizado utiliza el objeto al que pertenece como objeto monitor.

Solo un hilo puede ejecutarse dentro de un bloque de código Java sincronizado en el mismo objeto monitor.

Los siguientes dos ejemplos están ambos sincronizados en la instancia en la que se llaman. Por lo tanto, son equivalentes con respecto a la sincronización:

1
public class MiClase {
2
public synchronized void log1(String msg1, String msg2) {
3
log.escribirLinea(msg1);
4
log.escribirLinea(msg2);
5
}
6
7
public void log2(String msg1, String msg2) {
8
synchronized(this) {
9
log.escribirLinea(msg1);
10
log.escribirLinea(msg2);
11
}
12
}
13
}

Así, solo un único hilo puede ejecutarse dentro de cualquiera de los dos bloques sincronizados en este ejemplo.

Si el segundo bloque sincronizado hubiera estado sincronizado en un objeto diferente a “this”, entonces un hilo a la vez habría podido ejecutarse dentro de cada método.

Ejemplo de Sincronización en Java

Este ejemplo inicia 2 hilos y hace que ambos llamen al método sumar en la misma instancia de Contador. Solo un hilo a la vez podrá llamar al método sumar en la misma instancia, porque el método está sincronizado en la instancia a la que pertenece.

1
public class Ejemplo {
2
public static void main(String[] args) {
3
Contador contador = new Contador();
4
Thread hiloA = new HiloContador(contador);
5
Thread hiloB = new HiloContador(contador);
6
7
hiloA.start();
8
hiloB.start();
9
}
10
}

Estas son las dos clases utilizadas en el ejemplo anterior, Contador e HiloContador.

1
public class Contador {
2
long cuenta = 0;
3
4
public synchronized void sumar(long valor) {
5
this.cuenta += valor;
6
}
7
}
8
9
public class HiloContador extends Thread {
10
protected Contador contador = null;
11
12
public HiloContador(Contador contador) {
13
this.contador = contador;
14
}
15
16
public void run() {
17
for(int i = 0; i < 10; i++) {
18
contador.sumar(i);
19
}
20
}
21
}

Se crean dos hilos. La misma instancia de Contador se pasa a ambos en su constructor. El método Contador.sumar() está sincronizado en la instancia, porque el método sumar() es un método de instancia y está marcado como sincronizado. Por lo tanto, solo uno de los hilos puede llamar al método sumar() a la vez. El otro hilo esperará hasta que el primer hilo salga del método sumar(), antes de poder ejecutar el método él mismo.

Si los dos hilos hubieran hecho referencia a dos instancias separadas de Contador, no habría habido problemas para llamar a los métodos sumar() simultáneamente. Las llamadas habrían sido a diferentes objetos, por lo que los métodos llamados también estarían sincronizados en diferentes objetos (el objeto que posee el método). Por lo tanto, las llamadas no se bloquearían. Esto se puede ver como sigue:

1
public class Ejemplo {
2
public static void main(String[] args) {
3
Contador contadorA = new Contador();
4
Contador contadorB = new Contador();
5
Thread hiloA = new HiloContador(contadorA);
6
Thread hiloB = new HiloContador(contadorB);
7
8
hiloA.start();
9
hiloB.start();
10
}
11
}

Observemos cómo los dos hilos, hiloA e hiloB, ya no hacen referencia a la misma instancia de contador. El método sumar de contadorA y contadorB está sincronizado en sus dos instancias propias. Llamar a sumar() en contadorA no bloqueará una llamada a sumar() en contadorB.