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ámetroque parece que especificamos entre paréntesis después de la palabra clavesynchronized. 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.