¿Por qué usar ReentrantLock si uno puede usar synchronized (this)?

Estoy tratando de entender qué hace que el locking en la concurrencia sea tan importante si se puede usar synchronized (this) . En el código ficticio a continuación, puedo hacer lo siguiente:

  1. sincronizó todo el método o sincronizó el área vulnerable (sincronizado (esto) {…})
  2. O bloquee el área de código vulnerable con ReentrantLock.

Código:

  private final ReentrantLock lock = new ReentrantLock(); private static List ints; public Integer getResult(String name) { . . . lock.lock(); try { if (ints.size()==3) { ints=null; return -9; } for (int x=0; x>>>"+ints.get(x)); } } finally { lock.unlock(); } return random; } 

Un ReentrantLock no está estructurado , a diferencia de synchronized construcciones synchronized , es decir, no necesita usar una estructura de bloques para bloquear e incluso puede mantener un locking entre los métodos. Un ejemplo:

 private ReentrantLock lock; public void foo() { ... lock.lock(); ... } public void bar() { ... lock.unlock(); ... } 

Tal flujo es imposible de representar a través de un solo monitor en una construcción synchronized .


Además de eso, ReentrantLock admite consultas de locking y esperas de locking interrumpible que admiten tiempo de espera . ReentrantLock también tiene soporte para la política de equidad configurable , lo que permite una progtwigción de hilos más flexible.

El constructor de esta clase acepta un parámetro de equidad opcional. Cuando se establece como true , bajo contención, los lockings favorecen el acceso al hilo de espera más larga. De lo contrario, este locking no garantiza ninguna orden de acceso particular. Los progtwigs que utilizan lockings imparciales a los que acceden muchos subprocesos pueden mostrar un rendimiento general más bajo (es decir, son más lentos, a menudo mucho más lentos) que aquellos que usan la configuración predeterminada, pero tienen menores variaciones en los tiempos para obtener lockings y garantizar la ausencia de inanición. Sin embargo, tenga en cuenta que la imparcialidad de los lockings no garantiza la imparcialidad de la progtwigción del hilo. Por lo tanto, uno de los muchos subprocesos que utilizan un locking justo puede obtenerlo varias veces seguidas, mientras que otros subprocesos activos no avanzan y no mantienen el locking en ese momento. También tenga en cuenta que el método tryLock sin tryLock no tryLock la configuración de equidad. Sucederá si el locking está disponible incluso si hay otros hilos esperando.


ReentrantLock también puede ser más escalable , y se desempeña mucho mejor bajo una mayor contención. Puedes leer más sobre esto aquí .

Sin embargo, este reclamo ha sido impugnado; mira el siguiente comentario:

En la prueba de locking de reentrada, se crea un nuevo locking cada vez, por lo que no hay locking exclusivo y los datos resultantes no son válidos. Además, el enlace de IBM no ofrece ningún código fuente para el índice de referencia subyacente, por lo que es imposible caracterizar si la prueba se realizó incluso correctamente.


¿Cuándo deberías usar ReentrantLock s? De acuerdo con ese artículo developerWorks …

La respuesta es bastante simple: úsala cuando realmente necesites algo que no está synchronized , como esperas de locking temporizado, esperas de locking interrumpible, lockings sin estructura de bloques, múltiples variables de condición o sondeo de locking. ReentrantLock también tiene beneficios de escalabilidad, y debe usarlo si realmente tiene una situación que presenta una gran controversia, pero recuerde que la gran mayoría de synchronized bloques synchronized casi nunca presentan ninguna contención, y mucho menos una alta contención. Aconsejaría desarrollar con sincronización hasta que la sincronización haya demostrado ser inadecuada, en lugar de simplemente asumir que “el rendimiento será mejor” si usa ReentrantLock . Recuerde, estas son herramientas avanzadas para usuarios avanzados. (Y los usuarios realmente avanzados tienden a preferir las herramientas más simples que pueden encontrar hasta que están convencidos de que las herramientas simples son inadecuadas). Como siempre, primero haga las cosas bien, y luego preocúpese si debe hacerlo más rápido o no.

Un locking de reentrada permitirá que el titular del locking ingrese bloques de código incluso después de que ya haya obtenido el locking al ingresar otros bloques de código. Un locking no reentrante tendría el bloqueador de locking sobre sí mismo, ya que tendría que liberar el locking que obtuvo de otro bloque de código para volver a obtener ese mismo locking para ingresar al locking nested que requiere bloque de código

  public synchronized void functionOne() { // do something functionTwo(); // do something else // redundant, but permitted... synchronized(this) { // do more stuff } } public synchronized void functionTwo() { // do even more stuff! } 

Las capacidades extendidas del locking de reentrada incluyen:

  1. La capacidad de tener más de una variable de condición por monitor. Los monitores que usan la palabra clave sincronizada solo pueden tener uno. Esto significa que los lockings reentrantes admiten más de una cola wait () / notify ().
  2. La capacidad de hacer que el locking sea “justo”. “los lockings [justos] favorecen el acceso al hilo de espera más larga. De lo contrario, este locking no garantiza ninguna orden de acceso en particular”. Los bloques sincronizados son injustos.
  3. La capacidad de verificar si el locking se está reteniendo.
  4. La capacidad de obtener la lista de hilos esperando en el candado.

Las desventajas de los lockings reentrantes son:

Necesita agregar statement de importación. Es necesario ajustar las adquisiciones de locking en un bloque try / finally. Esto lo hace más feo que la palabra clave sincronizada. La palabra clave sincronizada se puede poner en las definiciones de métodos, lo que evita la necesidad de un bloque que reduzca la anidación.

Cuándo usar :-

  1. ReentrantLock podría ser más apropiado para usar si necesita implementar un hilo que atraviesa una lista vinculada, bloqueando el siguiente nodo y luego desbloqueando el nodo actual.
  2. La palabra clave sincronizada es apta en situaciones tales como el engrosamiento de locking, proporciona giro adaptable, locking sesgado y la posibilidad de elisión de locking a través de análisis de escape. Esas optimizaciones no están implementadas actualmente para ReentrantLock.

Para más información

ReentrantReadWriteLock es un locking especializado mientras que synchronized(this) es un locking de propósito general. Son similares pero no son lo mismo.

Tiene razón en que podría usar synchronized(this) lugar de ReentrantReadWriteLock pero lo contrario no siempre es cierto.

Si desea comprender mejor qué hace que ReentrantReadWriteLock busque alguna información especial sobre la sincronización de subprocesos productor-consumidor.

En general, puede recordar que la sincronización de todo el método y la sincronización de propósito general (usando la palabra clave synchronized ) pueden usarse en la mayoría de las aplicaciones sin pensar demasiado en la semántica de la sincronización, pero si necesita exprimir el rendimiento de su código, es posible que necesite para explorar otros mecanismos de sincronización más finos o especiales.

Por cierto, usar synchronized(this ) y en general bloquear usando una instancia de clase pública, puede ser problemático porque abre tu código a potenciales lockings porque alguien más no podría intentar bloquear tu objeto en otro lugar en el progtwig.

Desde la página de documentación de Oracle sobre ReentrantLock :

Una exclusión mutua reentrante Bloquee con el mismo comportamiento básico y semántica que el locking de monitor implícito al que se accede utilizando métodos y declaraciones sincronizados, pero con capacidades extendidas.

  1. A ReentrantLock pertenece al último locking exitoso de la secuencia, pero aún no la ha desbloqueado. Un hilo que invoque el locking volverá, adquiriendo con éxito el locking, cuando el locking no sea propiedad de otro hilo. El método volverá inmediatamente si el hilo actual ya posee el locking.

  2. El constructor de esta clase acepta un parámetro de equidad opcional. Cuando se establece como verdadero, bajo contención, los lockings favorecen el acceso al hilo de espera más larga . De lo contrario, este locking no garantiza ninguna orden de acceso particular.

ReentrantLock características clave según este artículo

  1. Posibilidad de bloquear de forma interrumpible.
  2. Capacidad de tiempo de espera mientras espera el locking.
  3. Poder para crear locking justo.
  4. API para obtener la lista de hilo en espera para el locking.
  5. Flexibilidad para intentar bloquear sin bloquear.

Puede usar ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock para adquirir más control sobre el locking granular en las operaciones de lectura y escritura.

Eche un vistazo a este artículo de Benjamen sobre el uso de diferentes tipos de ReentrantLocks

Puede usar lockings de reentrada con una política de equidad o tiempo de espera para evitar la falta de hilo. Puede aplicar una política de honestidad de subprocesos. ayudará a evitar un hilo que espere siempre para obtener sus recursos.

 private final ReentrantLock lock = new ReentrantLock(true); //the param true turns on the fairness policy. 

La “política de equidad” selecciona el siguiente hilo ejecutable para ejecutar. Se basa en la prioridad, el tiempo desde la última ejecución, bla, bla

Además, Synchronize puede bloquearse indefinidamente si no puede escapar del bloque. Reentrantlock puede tener tiempo de espera establecido.