icono de candadoUno de los objetivos principales de Java Standard Edition 6 (Mustang) era mejorar la performance y escalabilidad de las aplicaciones. Esto se logró mayormente mediante optimizaciones en la performance en tiempo de ejecución, un mejor gargabe collector, y varias mejoras en los tiempos de inicio de la máquina virtual.

Las optimizaciones de performance del entorno de ejecución incluyen nuevas estrategias para los monitores de bloques sincronizados. Estas técnicas para mejorar la peformance implementan el lockeo con preferencia, lockeo amplio y la espera adaptativa.

Lockeo con preferencia (biased locking)

El lockeo con preferencia se logra cuando es posible evitar la obtención y liberación de los objetos de lock cuando no son necesarios. Esta estrategia se basa en el hecho que, en la mayoría de los casos, los bloques sincronizados no son competidos simultáneamente. Más aún, la mayoría de los monitores tan sólo son accedidos por un único thread durante toda su vida. Es importante recordar que existen múltiples monitores y lockeos internos, más allá de los bloques sincronizados que uno escriba explícitamente en el código.

Es facil entonces ver que esta situación (el mismo thread obtiene y libera el lockeo una y otra vez) es una pérdida de tiempo. De aquí surge el concepto del lockeo con preferencia. Los bloques sincronizados le dan preferencia al primer thread que los accede. Si este mismo thread necesita volver a acceder al bloque sincronizado, no necesitará obtener el lock. Sin embargo, si algún otro thread accede al bloque, se necesita quitar esta preferencia, lo cual es costoso.

Esta estrategia tiene sentido cuando lo que se gana por eliminar estas operaciones atómicas es mayor al costo por revocar la preferencia.

Esta característica se encuentra activada desde JSE 6 (puede desactivarse con el parámetro -XX:-UseBiasedLocking)

Lockeo amplio (lock coarsening)

El lockeo amplio ocurre en situaciones en las que no ocurren operaciones notorias entre la adquisición y la liberación del lock. Por ejemplo, imaginen una porción de código en donde se agregan objetos a un Vector de manera consecutiva (recuerden, el Vector es una implementación sincronizada de colecciones). Aquí, cada sentencia adquiere el lock y la libera.

El compilador entonces puede detectar que hay una secuencia de bloques que operan sobre el mismo lock, y los puede unir en un único bloque. Así, todo el bloque se convierte en un único bloque sincronizado, eliminando el costo de múltiples adquisiciones y liberaciones del lock.

Esta técnica no se utiliza si el compilador detecta la presencia de loops, ya que podría obtenerse un lock por mucho tiempo.

Esta característica se encuentra activada desde JSE 6 (puede desactivarse con el parámetro -XX:-EliminateLocks)

Espera adaptativa (adaptative spinning)

La espera adaptativa ocurre cuando un thread intenta acceder a un bloque sincronizado, pero no obtiene el lock. En esta situación, el thread se queda en "espera activa" en vez de hacer un context switch. La cantidad de tiempo que el thread se queda esperando activamente es adaptativa, dependiendo de diferentes factores (como ser la tasa de éxitos/fracasos de esperas, el estado del bloqueo, etc.).


Inspiración.

"Si tú tienes una manzana y yo tengo una manzana e intercambiamos las manzanas, entonces tanto tú como yo seguiremos teniendo una manzana cada uno. Pero si tú tienes una idea y yo tengo una idea, e intercambiamos las ideas, entonces ambos tendremos dos ideas"

Bernard Shaw