Me pregunto cuál de estos es mejor en la práctica y por qué.
He descubierto que Lock
y Condition
(y otras concurrent
clases nuevas ) son solo más herramientas para la caja de herramientas. Podía hacer casi todo lo que necesitaba con mi viejo martillo (la synchronized
palabra clave), pero era difícil de usar en algunas situaciones. Varias de esas situaciones incómodas se volvieron mucho más simples una vez que agregué más herramientas a mi caja de herramientas: un mazo de goma, un martillo de bola, una palanca y algunos punzones. Sin embargo , mi viejo martillo todavía ve su parte de uso.
No creo que uno sea realmente "mejor" que el otro, sino que cada uno es mejor para diferentes problemas. En pocas palabras, el modelo simple y la naturaleza orientada al alcance de synchronized
me ayudan a protegerme de los errores en mi código, pero esas mismas ventajas son a veces obstáculos en escenarios más complejos. Es estos escenarios más complejos que el paquete concurrente se creó para ayudar a abordar. Pero el uso de estas construcciones de nivel superior requiere una administración más explícita y cuidadosa en el código.
===
Creo que JavaDoc hace un buen trabajo al describir la distinción entre Lock
y synchronized
(el énfasis es mío):
Las implementaciones de bloqueo proporcionan operaciones de bloqueo más extensas que las que se pueden obtener utilizando métodos y declaraciones sincronizados. Permiten una estructuración más flexible , pueden tener propiedades bastante diferentes y pueden admitir múltiples objetos de condición asociados .
...
El uso de métodos o sentencias sincronizadas proporciona acceso al bloqueo de monitor implícito asociado con cada objeto, pero obliga a que todas las adquisiciones y liberaciones de bloqueos ocurran de forma estructurada en bloques : cuando se adquieren múltiples bloqueos , deben liberarse en el orden opuesto , y todos los bloqueos deben liberarse en el mismo ámbito léxico en el que fueron adquiridos .
Si bien el mecanismo de definición del alcance para métodos y declaraciones sincronizados hace que sea mucho más fácil programar con bloqueos de monitor y ayuda a evitar muchos errores de programación comunes que involucran bloqueos, hay ocasiones en las que necesita trabajar con bloqueos de una manera más flexible. Por ejemplo, * * algunos algoritmos * para atravesar estructuras de datos a las que se accede simultáneamente requieren el uso de "mano sobre mano" o "bloqueo de cadena" : adquiere el bloqueo del nodo A, luego el nodo B, luego suelta A y adquiere C, luego suelte B y adquiera D y así sucesivamente. Las implementaciones de la interfaz de bloqueo permiten el uso de tales técnicas al permitir que un bloqueo sea adquirido y liberado en diferentes ámbitos , ypermitiendo la adquisición y liberación de múltiples bloqueos en cualquier orden .
Con esta mayor flexibilidad viene una responsabilidad adicional . La ausencia de bloqueo estructurado por bloques elimina la liberación automática de bloqueos que ocurre con los métodos y las declaraciones sincronizadas. En la mayoría de los casos, se debe usar el siguiente idioma:
...
Cuando el bloqueo y el desbloqueo se producen en diferentes ámbitos , se debe tener cuidado para garantizar que todo el código que se ejecuta mientras se mantiene el bloqueo esté protegido por try-finally o try-catch para garantizar que el bloqueo se libere cuando sea necesario.
Las implementaciones de bloqueo proporcionan una funcionalidad adicional sobre el uso de métodos y sentencias sincronizadas al proporcionar un intento sin bloqueo para adquirir un bloqueo (tryLock ()), un intento de adquirir el bloqueo que se puede interrumpir (lockInterruptibly (), y un intento de adquirir el bloqueo que puede expirar (tryLock (long, TimeUnit)).
...