Saltearse al contenido

Sincronización y visibilidad

Sincronización y visibilidad de datos

Sin el uso de la palabra clave synchronized (o la palabra clave volatile de Java), no hay garantía de que cuando un hilo cambie el valor de una variable compartida con otros hilos (por ejemplo, a través de un objeto al que todos los hilos tienen acceso), los otros hilos puedan ver el valor cambiado. No hay garantías sobre cuándo una variable mantenida en un registro de CPU por un hilo se “confirma” en la memoria principal, y no hay garantía sobre cuándo otros hilos “actualizan” una variable mantenida en un registro de CPU desde la memoria principal.

La palabra clave synchronized cambia eso. Cuando un hilo entra en un bloque sincronizado, actualizará los valores de todas las variables visibles para el hilo. Cuando un hilo sale de un bloque sincronizado, todos los cambios en las variables visibles para el hilo se confirmarán en la memoria principal.

Qué objetos sincronizar

Como se mencionó varias veces aquí, un bloque sincronizado debe estar sincronizado sobre algún objeto. En realidad, podemos elegir cualquier objeto para sincronizar, pero se recomienda que no sincronicemos en objetos String, ni en objetos envoltorios de tipos primitivos, ya que el compilador podría optimizarlos, de modo que estemos usando las mismas instancias en diferentes lugares de nuestro código donde pensábamos que estábamos usando instancias diferentes. Veamos este ejemplo:

1
synchronized("Hola") {
2
// hacer algo aquí.
3
}

Si tenemos más de un bloque sincronizado que está sincronizado en el valor literal de String “Hola”, entonces el compilador podría usar el mismo objeto String detrás de escena. El resultado es que estos dos bloques sincronizados están sincronizados en el mismo objeto. Ese podría no ser el comportamiento que estábamos buscando.

Lo mismo puede ser cierto para el uso de objetos envoltorios de tipos primitivos. Veamos otro ejemplo:

1
synchronized(Integer.valueOf(1)) {
2
// hacer algo aquí.
3
}

Si llamamos a Integer.valueOf(1) varias veces, podría devolver la misma instancia de objeto envoltorio para los mismos valores de parámetros de entrada. Eso significa que si estamos sincronizando múltiples bloques en el mismo objeto envoltorio primitivo (por ejemplo, usar Integer.valueOf(1) varias veces como objeto monitor), entonces nos arriesgamos a que esos bloques sincronizados se sincronicen todos en el mismo objeto. Ese tampoco podría ser el comportamiento que buscábamos.

Para estar seguros, podemos sincronizar en this - o en un new Object(). Estos no son almacenados en caché ni reutilizados internamente por el compilador de Java, la JVM o las bibliotecas de Java.