Bloqueo de reentrada en Java

Fondo

La forma tradicional de lograr la sincronización de subprocesos en Java es mediante el uso de la palabra clave sincronizada . Si bien proporciona cierta sincronización básica, la palabra clave sincronizada es bastante rígida en su uso. Por ejemplo, un subproceso puede tomar un bloqueo solo una vez. Los bloques sincronizados no ofrecen ningún mecanismo de cola de espera y después de la salida de un hilo, cualquier hilo puede tomar el bloqueo. Esto podría conducir a la escasez de recursos para algún otro subproceso durante un período de tiempo muy largo. 
Los bloqueos de reentrada se proporcionan en Java para proporcionar una sincronización con mayor flexibilidad.  

¿Qué son las cerraduras reentrantes?

La clase ReentrantLock implementa la interfaz Lock y proporciona sincronización a los métodos al acceder a los recursos compartidos. El código que manipula el recurso compartido está rodeado de llamadas al método de bloqueo y desbloqueo. Esto le da un bloqueo al subproceso de trabajo actual y bloquea todos los demás subprocesos que intentan bloquear el recurso compartido. 

Como su nombre indica, ReentrantLock permite que los subprocesos entren en el bloqueo de un recurso más de una vez. Cuando el subproceso entra por primera vez en el candado, el recuento de espera se establece en uno. Antes de desbloquear, el hilo puede volver a entrar en el bloqueo nuevamente y cada vez que el recuento de espera se incrementa en uno. Por cada solicitud de desbloqueo, el recuento de espera se reduce en uno y cuando el recuento de espera es 0, el recurso se desbloquea. 

Los bloqueos reentrantes también ofrecen un parámetro de equidad, por el cual el bloqueo cumpliría con el orden de la solicitud de bloqueo, es decir, después de que un subproceso desbloquea el recurso, el bloqueo iría al subproceso que ha estado esperando durante más tiempo. Este modo de equidad se configura pasando verdadero al constructor de la cerradura. 
Estos candados se utilizan de la siguiente manera:  

Java

public void some_method()
{
        reentrantlock.lock();
        try
        {
            //Do some work
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            reentrantlock.unlock();
        }
         
}

 

La declaración de desbloqueo siempre se llama en el bloque finalmente para garantizar que el bloqueo se libere incluso si se lanza una excepción en el cuerpo del método (bloque de prueba).

Métodos ReentrantLock() 

  • lock(): la llamada al método lock() incrementa el recuento de espera en 1 y otorga el bloqueo al subproceso si el recurso compartido inicialmente está libre.
  • unlock(): la llamada al método unlock() disminuye el recuento de espera en 1. Cuando este recuento llega a cero, se libera el recurso.
  • tryLock(): si el recurso no está retenido por ningún otro subproceso, la llamada a tryLock() devuelve verdadero y el recuento de retenciones se incrementa en uno. Si el recurso no está libre, el método devuelve falso y el subproceso no se bloquea, sino que se cierra.
  • tryLock (tiempo de espera prolongado, unidad de unidad de tiempo): según el método, el subproceso espera un cierto período de tiempo definido por los argumentos del método para adquirir el bloqueo en el recurso antes de salir.
  • lockInterruptably(): este método adquiere el bloqueo si el recurso está libre y permite que el subproceso sea interrumpido por otro subproceso mientras adquiere el recurso. Significa que si el subproceso actual está esperando el bloqueo pero otro subproceso solicita el bloqueo, entonces el subproceso actual se interrumpirá y regresará inmediatamente sin adquirir el bloqueo.
  • getHoldCount(): este método devuelve el recuento de la cantidad de bloqueos retenidos en el recurso.
  • isHeldByCurrentThread(): este método devuelve verdadero si el hilo actual mantiene el bloqueo del recurso.

Ejemplo de ReentrantLock()

En el siguiente tutorial, veremos un ejemplo básico de Reentrant Locks.

Pasos a seguir  

1. Create an object of ReentrantLock
2. Create a worker(Runnable Object) to execute and pass the lock to the object
3. Use the lock() method to acquire the lock on shared resource
4. After completing work, call unlock() method to release the lock 

A continuación se muestra la implementación del enunciado del problema:

Java

// Java code to illustrate Reentrant Locks
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
 
class worker implements Runnable
{
  String name;
  ReentrantLock re;
  public worker(ReentrantLock rl, String n)
  {
    re = rl;
    name = n;
  }
  public void run()
  {
    boolean done = false;
    while (!done)
    {
      //Getting Outer Lock
      boolean ans = re.tryLock();
 
      // Returns True if lock is free
      if(ans)
      {
        try
        {
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("task name - "+ name
                     + " outer lock acquired at "
                     + ft.format(d)
                     + " Doing outer work");
          Thread.sleep(1500);
 
          // Getting Inner Lock
          re.lock();
          try
          {
            d = new Date();
            ft = new SimpleDateFormat("hh:mm:ss");
            System.out.println("task name - "+ name
                       + " inner lock acquired at "
                       + ft.format(d)
                       + " Doing inner work");
            System.out.println("Lock Hold Count - "+ re.getHoldCount());
            Thread.sleep(1500);
          }
          catch(InterruptedException e)
          {
            e.printStackTrace();
          }
          finally
          {
            //Inner lock release
            System.out.println("task name - " + name +
                       " releasing inner lock");
 
            re.unlock();
          }
          System.out.println("Lock Hold Count - " + re.getHoldCount());
          System.out.println("task name - " + name + " work done");
 
          done = true;
        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
        finally
        {
          //Outer lock release
          System.out.println("task name - " + name +
                     " releasing outer lock");
 
          re.unlock();
          System.out.println("Lock Hold Count - " +
                       re.getHoldCount());
        }
      }
      else
      {
        System.out.println("task name - " + name +
                      " waiting for lock");
        try
        {
          Thread.sleep(1000);
        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
      }
    }
  }
}
 
public class test
{
  static final int MAX_T = 2;
  public static void main(String[] args)
  {
    ReentrantLock rel = new ReentrantLock();
    ExecutorService pool = Executors.newFixedThreadPool(MAX_T);
    Runnable w1 = new worker(rel, "Job1");
    Runnable w2 = new worker(rel, "Job2");
    Runnable w3 = new worker(rel, "Job3");
    Runnable w4 = new worker(rel, "Job4");
    pool.execute(w1);
    pool.execute(w2);
    pool.execute(w3);
    pool.execute(w4);
    pool.shutdown();
  }
}

Ejemplo de ejecución 

Output:
task name - Job2 waiting for lock
task name - Job1 outer lock acquired at 09:49:42 Doing outer work
task name - Job2 waiting for lock
task name - Job1 inner lock acquired at 09:49:44 Doing inner work
Lock Hold Count - 2
task name - Job2 waiting for lock
task name - Job2 waiting for lock
task name - Job1 releasing inner lock
Lock Hold Count - 1
task name - Job1 work done
task name - Job1 releasing outer lock
Lock Hold Count - 0
task name - Job3 outer lock acquired at 09:49:45 Doing outer work
task name - Job2 waiting for lock
task name - Job3 inner lock acquired at 09:49:47 Doing inner work
Lock Hold Count - 2
task name - Job2 waiting for lock
task name - Job2 waiting for lock
task name - Job3 releasing inner lock
Lock Hold Count - 1
task name - Job3 work done
task name - Job3 releasing outer lock
Lock Hold Count - 0
task name - Job4 outer lock acquired at 09:49:48 Doing outer work
task name - Job2 waiting for lock
task name - Job4 inner lock acquired at 09:49:50 Doing inner work
Lock Hold Count - 2
task name - Job2 waiting for lock
task name - Job2 waiting for lock
task name - Job4 releasing inner lock
Lock Hold Count - 1
task name - Job4 work done
task name - Job4 releasing outer lock
Lock Hold Count - 0
task name - Job2 outer lock acquired at 09:49:52 Doing outer work
task name - Job2 inner lock acquired at 09:49:53 Doing inner work
Lock Hold Count - 2
task name - Job2 releasing inner lock
Lock Hold Count - 1
task name - Job2 work done
task name - Job2 releasing outer lock
Lock Hold Count - 0

Puntos importantes  

  1. Uno puede olvidarse de llamar al método unlock() en el bloque finalmente, lo que genera errores en el programa. Asegúrese de que el bloqueo se libere antes de que salga el hilo.
  2. El parámetro de equidad utilizado para construir el objeto de bloqueo disminuye el rendimiento del programa.

El ReentrantLock es un mejor reemplazo para la sincronización, que ofrece muchas funciones que no ofrece sincronizado. Sin embargo, la existencia de estos beneficios obvios no es razón suficiente para preferir siempre ReentrantLock para sincronizar. En su lugar, tome la decisión sobre la base de si necesita la flexibilidad que ofrece ReentrantLock.

Este artículo es una contribución de Abhishek . Si le gusta GeeksforGeeks y le gustaría contribuir, también puede escribir un artículo usando contribuya.geeksforgeeks.org o envíe su artículo por correo a contribuya@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.
Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente. 

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *