Bienvenido al tutorial de ejemplo de Java Lock. Normalmente, cuando trabajamos en un entorno con múltiples hilos, usamos synchronized para la seguridad de los hilos.
Java Lock
La mayoría de las veces, la palabra clave synchronized es la opción preferida, pero tiene algunas deficiencias que llevaron a la inclusión de la API Lock en el paquete de concurrencia de Java. La API de concurrencia de Java 1.5 introdujo el paquete
java.util.concurrent.locks
con la interfaz Lock
y algunas clases de implementación para mejorar el mecanismo de bloqueo de objetos. Algunas interfaces y clases importantes en la API de Lock de Java son:
-
Lock: Esta es la interfaz base para la API Lock. Proporciona todas las características de la palabra clave synchronized con formas adicionales de crear diferentes Condiciones para el bloqueo, proporcionando un tiempo de espera para que el hilo espere el bloqueo. Algunos de los métodos importantes son lock() para adquirir el bloqueo, unlock() para liberar el bloqueo, tryLock() para esperar el bloqueo durante un cierto período de tiempo, newCondition() para crear la Condición, etc.
-
Condición: Los objetos de Condición son similares al modelo espera-notificación de Objeto con la característica adicional de crear diferentes conjuntos de espera. Un objeto de Condición siempre es creado por un objeto de Bloqueo. Algunos de los métodos importantes son await() que es similar a wait() y signal(), signalAll() que es similar a los métodos notify() y notifyAll().
-
Bloqueo de Lectura/Escritura: Contiene un par de bloqueos asociados, uno para operaciones de solo lectura y otro para escritura. El bloqueo de lectura puede ser sostenido simultáneamente por múltiples hilos lectores siempre y cuando no haya hilos escritores. El bloqueo de escritura es exclusivo.
-
ReentrantLock: Esta es la clase de implementación más ampliamente utilizada de la interfaz Lock. Esta clase implementa la interfaz Lock de manera similar a la palabra clave synchronized. Aparte de la implementación de la interfaz Lock, ReentrantLock contiene algunos métodos de utilidad para obtener el hilo que tiene la cerradura, los hilos esperando para adquirir la cerradura, etc. Los bloques sincronizados son reentrantes en naturaleza, es decir, si un hilo tiene la cerradura en el objeto monitor y si otro bloque sincronizado necesita tener la cerradura en el mismo objeto monitor, entonces el hilo puede ingresar a ese bloque de código. Creo que esta es la razón por la cual el nombre de la clase es ReentrantLock. Entendamos esta característica con un ejemplo simple.
public class Test{ public synchronized foo(){ //hacer algo bar(); } public synchronized bar(){ //hacer algo más } }
Si un hilo entra en foo(), tiene la cerradura en el objeto Test, por lo que cuando intenta ejecutar el método bar(), se le permite al hilo ejecutar el método bar() ya que ya tiene la cerradura en el objeto Test, es decir, lo mismo que synchronized(this).
Ejemplo de Bloqueo en Java – ReentrantLock en Java
Ahora veamos un ejemplo simple donde reemplazaremos la palabra clave synchronized con la API de Bloqueo de Java. Digamos que tenemos una clase Resource con algunas operaciones donde queremos que sea segura para subprocesos y algunos métodos donde no se requiere seguridad para subprocesos.
package com.journaldev.threads.lock;
public class Resource {
public void doSomething(){
//realizar alguna operación, lectura y escritura en la base de datos, etc.
}
public void doLogging(){
//registro, no se necesita seguridad para subprocesos
}
}
Ahora supongamos que tenemos una clase Runnable en la que utilizaremos los métodos de recursos.
package com.journaldev.threads.lock;
public class SynchronizedLockExample implements Runnable{
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
Observa que estoy usando un bloque sincronizado para adquirir el bloqueo en el objeto de recurso. Podríamos haber creado un objeto ficticio en la clase y usarlo con fines de bloqueo. Ahora veamos cómo podemos usar la API de bloqueo de Java y reescribir el programa anterior sin usar la palabra clave synchronized. Utilizaremos ReentrantLock en Java.
package com.journaldev.threads.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//liberar bloqueo
lock.unlock();
}
resource.doLogging();
}
}
Como puedes ver, estoy utilizando el método tryLock() para asegurarme de que mi hilo espere solo durante un tiempo definido y, si no obtiene el bloqueo del objeto, solo registra y sale. Otro punto importante a tener en cuenta es el uso del bloque try-finally para asegurarse de que el bloqueo se libere incluso si la llamada al método doSomething() lanza alguna excepción.
Java Lock vs synchronized
Basándonos en los detalles y el programa anteriores, podemos concluir fácilmente las siguientes diferencias entre Java Lock y la sincronización.
- La API de bloqueo de Java proporciona más visibilidad y opciones para el bloqueo, a diferencia de synchronized, donde un hilo podría quedarse esperando indefinidamente el bloqueo, podemos usar tryLock() para asegurarnos de que el hilo espere solo durante un tiempo específico.
- El código de sincronización es mucho más limpio y fácil de mantener, mientras que con Lock estamos obligados a tener un bloque try-finally para asegurarnos de que el Lock se libere incluso si se produce alguna excepción entre las llamadas a los métodos lock() y unlock().
- bloques o métodos de sincronización solo pueden cubrir un método, mientras que podemos adquirir el bloqueo en un método y liberarlo en otro método con la API de Lock.
- La palabra clave synchronized no proporciona equidad, mientras que podemos establecer la equidad como verdadera al crear un objeto ReentrantLock para que el hilo que lleva más tiempo esperando obtenga el bloqueo primero.
- Podemos crear diferentes condiciones para Lock y diferentes hilos pueden esperar() por diferentes condiciones.
Eso es todo para el ejemplo de bloqueo en Java, ReentrantLock en Java y un análisis comparativo con la palabra clave synchronized.
Source:
https://www.digitalocean.com/community/tutorials/java-lock-example-reentrantlock