Bloque synchronized

synchronized también puede usarse como un bloque, en el cuerpo de un método. Al hacerlo así, podemos especificar el objeto que queremos usar como monitor, y también obtener un control más granular sobre las líneas de código que queremos que se ejecuten bajo control de sincronización.

Otra forma de declarar una región de código que deba ser ejecutada con acceso sincronizado es utilizar bloques synchronized.

En este caso, lo que hacemos es utilizar directamente dentro del cuerpo de la función un bloque que lleva la palabra clave synchronized, y dentro del cual introduciremos las líneas de código que queramos que se ejecuten sincronizadamente. Tiene el siguiente aspecto:

synchronized (monitor) {
  /* Insertar código */
}

Apreciarás dos cosas:

  • Las llaves { } que lleva el bloque conforman el inicio y el fin de la región sincronizada. Cuando un hilo llegue al bloque synchronized, tratará de adquirir el monitor, y entonces empezará a ejecutar el interior del bloque. Cuando se llegue a la última línea de código del bloque y salga por la llave de cierre, liberará el monitor para que otro hilo pueda capturarlo.
  • Existe un parámetro que parece que especificamos entre paréntesis después de la palabra clave synchronized. Este parámetro es el monitor que queremos usar como referencia.

Así es, en el caso de un bloque synchronized, podemos especificar el monitor que queremos usar para controlar la ejecución de código. Entre paréntesis especificaremos una referencia a la instancia o clase que queramos emplear.

Por ejemplo, podríamos utilizar this para usar la propia instancia actual como monitor, de una forma parecida a como funciona synchronized a nivel de método.

synchronized (this) {
  /* Código */
}

Sin embargo, también podemos poner una instancia diferente. ¿Cuándo nos puede ser útil esto? Quizás una de las situaciones más prácticas sea cuando queramos utilizar un recurso compartido que no sea necesariamente thread-safe y que no utilice synchronized en la definición de sus métodos.

Por ejemplo, vamos a retomar el ejemplo que iniciamos en la lección anterior. Tenemos un recurso compartido denominado Timbre, con un método denominado timbrar, para hacerlo sonar. Y queremos impedir que más de un hilo pueda hacer sonar el timbre a la vez. Las manos tienen que pulsarlo de uno en uno.

En el ejemplo de la lección anterior acabamos usando un método synchronized para controlar esto. Sin embargo, si el método no tuviese synchronized, podríamos utilizar en el propio código del Runnable o del Thread un bloque synchronized que usase directamente el monitor del timbre.

@Override
public void run() {
  synchronized (timbre) {
    timbre.timbrar();
  }
}

Monitores especiales para el bloque synchronized

Usar Object como monitor

Aunque tal vez convenga usar Lock en algunas situaciones, otra cosa que podemos hacer para emplear bloques sincronizados es usar cualquier objeto. Esencialmente, todas las instancias tienen su propio monitor, y nadie va a comprobar quién es, únicamente si el monitor está pillado o no.

Por ejemplo, ¿sabías que puedes instanciar directamente un Object en Java? Sin subclase ni nada por el estilo, el siguiente código es válido:

Object o = new Object();

Ciertamente, este objeto no resulta útil porque no tiene métodos ni atributos. Sin embargo, nos da un monitor igualmente que podemos emplear como parte de un bloque synchronized. Esto sí nos puede resultar de utilidad.

Usar una clase como monitor

Podrías utilizar una clase como monitor. Esto te puede ser útil en el caso de métodos estáticos, por ejemplo, para que afecte a todas las invocaciones del método estático.

synchronized (Timbre.class) {
  /* Código */
}

Anidar bloques synchronized

Por último, otra cosa que puedes hacer cuando trabajas con bloques synchronized (aunque también puedes hacerlo con métodos, si lo organizas bien) es meter un bloque dentro de otro. De este modo, puedes definir una región de código que sólo se puede ejecutar una vez adquiere múltiples monitores.

@Override
public void run() {
  synchronized (input) {
    synchronized (output) {
      var content = input.read();
      output.push(content);
    }
  }
}

Ten en cuenta, eso sí, que el acceso debe ser organizado y que cuantos más recursos compartidos tengas que bloquear, más probable es que acabes con un interbloqueo, una situación que puede arruinar la ejecución de un programa y de la que te hablo más adelante en su lección.

Lista de reproducción
  1. 1
    ¿Qué es la concurrencia?
    8 minutos
  2. 2
    ¿Qué es un hilo?
    5 minutos
  3. 3
    Crear un hilo en Java
    10 minutos
  4. 4
    Interrupción de hilos
    10 minutos
  5. 5
    Cómo usar Thread.join
    14 minutos
  6. 6
    Corrupción de memoria
    8 minutos
  7. 7
    Monitores y synchronized
    10 minutos
  8. 8
    Bloque synchronized
    12 minutos
  9. 9
    Preguntas típicas sobre synchronized en Java
    8 minutos
  10. 10
    Interbloqueos, synchronized y el problema de la cena de los filósofos
    13 minutos
  11. 11
    Introducción a Executor en Java
    11 minutos
  12. 12
    Introducción al uso de ExecutorService en Java
    14 minutos
  13. 13
    Introducción a Thread Pools en Java
    10 minutos
  14. 14
    Cómo crear Thread Pools en Java
    11 minutos
  15. 15
    ¿Cómo funcionan wait() y notify()?
    10 minutos
  16. 16
    Ejemplo de wait() y notify() en Java
    9 minutos
  17. 17
    La palabra clave volatile
    8 minutos
  18. 18
    Cómo usar Future
    8 minutos
  19. 19
    Variables atómicas
    7 minutos