java - hilos - ¿Por qué usar un ReentrantLock si uno puede usar sincronizado(esto)?
sincronizacion de hilos en java (6)
Desde la página de documentación de oracle sobre ReentrantLock :
Un Bloqueo de exclusión mutua reentrante con el mismo comportamiento y semántica básicos que el bloqueo de monitor implícito al que se accede mediante métodos y declaraciones sincronizados, pero con capacidades ampliadas.
A ReentrantLock pertenece al subproceso que se bloquea con éxito, pero aún no lo está desbloqueando. Se devolverá un bloqueo de invocación de hilo, adquiriendo el bloqueo con éxito, cuando el bloqueo no sea propiedad de otro hilo. El método volverá inmediatamente si el subproceso actual ya posee el bloqueo.
El constructor de esta clase acepta un parámetro de imparcialidad opcional. Cuando se establece verdadero, en disputa, los bloqueos favorecen la concesión de acceso al subproceso de espera más larga . De lo contrario, este bloqueo no garantiza ningún orden de acceso particular.
ReentrantLock características clave según este article
- Posibilidad de bloquear de forma interrumpible.
- Posibilidad de tiempo de espera mientras se espera el bloqueo.
- Poder para crear un bloqueo justo.
- API para obtener la lista de hilos en espera para el bloqueo.
- Flexibilidad para tratar de bloqueo sin bloqueo.
Puede usar ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock para adquirir más control sobre el bloqueo granular en las operaciones de lectura y escritura.
Eche un vistazo a este article de Benjamen sobre el uso de diferentes tipos de ReentrantLocks
Estoy tratando de entender lo que hace que el bloqueo en la concurrencia sea tan importante si uno puede usar synchronized (this)
. En el siguiente código ficticio, puedo hacer:
- sincronizó todo el método o sincronizó el área vulnerable (sincronizado (esto) {...})
- O bloquee el área de código vulnerable con un ReentrantLock.
Código:
private final ReentrantLock lock = new ReentrantLock();
private static List<Integer> ints;
public Integer getResult(String name) {
.
.
.
lock.lock();
try {
if (ints.size()==3) {
ints=null;
return -9;
}
for (int x=0; x<ints.size(); x++) {
System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
}
} finally {
lock.unlock();
}
return random;
}
Puede usar los bloqueos de reentrada con una política de imparcialidad o un tiempo de espera para evitar el hambre de subprocesos. Puede aplicar una política de equidad de hilo. ayudará a evitar un hilo que espera por siempre para llegar a sus recursos.
private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy.
La "política de imparcialidad" elige el siguiente hilo ejecutable para ejecutar. Se basa en la prioridad, el tiempo transcurrido desde la última ejecución, bla bla
Además, Synchronize puede bloquearse indefinidamente si no puede escapar del bloque. Reentrantlock puede tener el tiempo de espera establecido.
Supongamos que este código se está ejecutando en un hilo:
private static ReentrantLock lock = new ReentrantLock(); void accessResource() { lock.lock(); if( checkSomeCondition() ) { accessResource(); } lock.unlock(); }
Debido a que el hilo posee el bloqueo, permitirá que se bloqueen varias llamadas (), por lo que volverá a entrar en el bloqueo. Esto se puede lograr con un recuento de referencia para que no tenga que volver a adquirir el bloqueo.
Un ReentrantLock no está estructurado , a diferencia de synchronized
construcciones synchronized
, es decir, no es necesario utilizar una estructura de bloque para el bloqueo e incluso puede mantener un bloqueo en todos 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
.
Aparte de eso, ReentrantLock
admite el sondeo de bloqueo y las esperas de bloqueo interrumpibles que soportan el tiempo de espera . ReentrantLock
también admite la política de equidad configurable , lo que permite una programación de subprocesos más flexible.
El constructor de esta clase acepta un parámetro de imparcialidad opcional. Cuando se establece
true
, en disputa, los bloqueos favorecen la concesión de acceso al subproceso de espera más larga. De lo contrario, este bloqueo no garantiza ningún orden de acceso particular. Los programas que usan bloqueos en condiciones de acceso 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 los que usan la configuración predeterminada, pero tienen variaciones más pequeñas en los tiempos para obtener bloqueos y garantizar la falta de inanición. Sin embargo, tenga en cuenta que la imparcialidad de los bloqueos no garantiza la imparcialidad de la programación de subprocesos. Por lo tanto, uno de los muchos subprocesos que utilizan un bloqueo justo puede obtenerlo varias veces seguidas, mientras que otros subprocesos activos no avanzan y no mantienen el bloqueo actualmente. También tenga en cuenta que el métodotryLock
sintryLock
notryLock
la configuración de imparcialidad. Tendrá éxito si el bloqueo está disponible incluso si hay otros subprocesos esperando.
ReentrantLock
también puede ser más escalable , con un rendimiento mucho mejor bajo la contención más alta. Puedes leer más sobre esto here .
Esta demanda ha sido impugnada, sin embargo; Vea el siguiente comentario:
En la prueba de bloqueo de reentrada, se crea un nuevo bloqueo cada vez, por lo que no hay un bloqueo 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ó correctamente.
¿Cuándo debes usar ReentrantLock
s? Según el artículo de developerWorks ...
La respuesta es bastante simple: utilícela cuando realmente necesite algo que proporcione que no esté
synchronized
, como espera de bloqueo temporizado, espera de bloqueo interrumpible, bloqueos no estructurados en bloque, variables de condición múltiples o sondeo de bloqueo.ReentrantLock
también tiene beneficios de escalabilidad, y debería usarlo si realmente tiene una situación que muestre alta contienda, pero recuerde que la gran mayoría desynchronized
bloquessynchronized
casi nunca exhiben ninguna contienda, por no hablar de alta contienda. Recomendaría desarrollar con sincronización hasta que la sincronización haya demostrado ser inadecuada, en lugar de simplemente suponer que "el rendimiento será mejor" si usaReentrantLock
. Recuerda, 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, haga las cosas bien primero y luego preocupe si tiene que hacerlo más rápido o no.
ReentrantReadWriteLock
es un bloqueo especializado, mientras que synchronized(this)
es un bloqueo de propósito general. Son similares pero no exactamente iguales.
Tiene razón en que podría usar synchronized(this)
lugar de ReentrantReadWriteLock
pero lo contrario no siempre es cierto.
Si desea comprender mejor lo que hace especial a ReentrantReadWriteLock
busque información 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 (usar la palabra clave synchronized
) se pueden usar en la mayoría de las aplicaciones sin pensar demasiado en la semántica de la sincronización, pero si necesita eliminar el rendimiento de su código, es posible que lo necesite. para explorar otros mecanismos de sincronización más específicos o con un propósito especial.
Por cierto, el uso synchronized(this
) y, en general, el bloqueo mediante una instancia de clase pública, puede ser problemático porque abre su código a posibles bloqueos directos, ya que otra persona, a sabiendas, podría intentar bloquear su objeto en otro lugar en el programa.
Los bloqueos sincronizados no ofrecen ningún mecanismo de cola de espera en el que, después de la ejecución de un hilo, cualquier hilo que se ejecuta en paralelo pueda adquirir el bloqueo. Debido a que el subproceso que está allí en el sistema y que se ejecuta durante un período de tiempo más largo nunca tiene la oportunidad de acceder al recurso compartido, lo que conduce a la inanición.
Los bloqueos de reentrada son muy flexibles y tienen una política de imparcialidad en la que si un subproceso está esperando durante más tiempo y luego de completar el subproceso que se está ejecutando actualmente, podemos asegurarnos de que el subproceso en espera más larga tenga la posibilidad de acceder al recurso compartido disminuyendo El rendimiento del sistema y lo hace más lento.