Variable sincronizada de Java

Shubham Vora 12 octubre 2023
  1. la palabra clave sincronizada
  2. Utilice el método ReentrantLock
  3. Usar el semáforo binario o de conteo
  4. Usar la variable atómica
Variable sincronizada de Java

Este artículo discutirá cómo sincronizar o bloquear variables en Java.

La sincronización o el bloqueo son esenciales en la programación de subprocesos múltiples. Los subprocesos que se ejecutan en paralelo pueden intentar acceder a la misma variable u otros recursos que producen resultados inesperados.

La sincronización o el bloqueo es una solución para evitar estos casos de error.

la palabra clave sincronizada

La sincronización es un método tradicional en Java. El bloque sincronizado se implementa utilizando la palabra clave sincronizado.

Una vez que un subproceso ingresa el código sincronizado, otros subprocesos de Java estarán en un estado de bloqueo. Después de que el subproceso Java actual sale del bloque “sincronizado”, otros subprocesos Java pueden intentar acceder al bloque “sincronizado”.

Hay un inconveniente en este método. El bloque sincronizado nunca permite que un subproceso espere en una cola y acceda a la variable después de que el subproceso Java actual termine su trabajo, por lo que los subprocesos Java deben esperar mucho tiempo.

Usar la palabra clave Sincronizada con un método o bloque

En el siguiente ejemplo, sincronizamos el MenuObj en el método run() de la clase MultiThreadList. También podemos definir el bloque de código listItem() con synchronized.

Ambas funciones darán el mismo resultado.

El mismo programa puede ser como sigue con la sincronización de métodos.

public synchronized void listItem(String item) {
  { /*Code to Synchronize*/
  }
}
synchronized (this) {
  System.out.println("\nMenu Item - \t" + item);
  try {
    Thread.sleep(1000);
  } catch (Exception e) {
    e.printStackTrace();
  }
  System.out.println("Listed - " + item);
}

Aquí, los usuarios pueden ver el código de ejemplo completo utilizando el bloque sincronizado.

Código de ejemplo:

import java.io.*;
import java.util.*;
// Menu class
class Menu {
  // List an item
  public void listItem(String item) {
    System.out.println("\nMenu Item - " + item);
    try {
      Thread.sleep(1000);
    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("Listed - " + item);
  }
}
// Multi-thread menu listing
class MultiThreadList extends Thread {
  private String item;
  Menu MenuObj;
  /* Gets menu object and a string item*/
  MultiThreadList(String m, Menu obj) {
    item = m;
    MenuObj = obj;
  }
  public void run() {
    /* Only one Java thread can list an item at a time.*/
    synchronized (MenuObj) {
      // Menu object synchronized
      MenuObj.listItem(item);
    }
  }
}
// Main
class MainSync {
  public static void main(String args[]) {
    Menu listItem = new Menu();
    MultiThreadList M1 = new MultiThreadList("Rice", listItem);
    MultiThreadList M2 = new MultiThreadList("Icecream", listItem);

    // Start two threads
    M1.start();
    M2.start();

    // Wait for thread completion
    try {
      M1.join();
      M2.join();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Producción :

Menu Item - Rice
Listed - Rice
Menu Item - Icecream
Listed - Icecream

En el resultado anterior, los usuarios pueden observar que cuando un subproceso accede a MenuObj, los otros subprocesos no pueden, ya que imprime el elemento de la lista seguido del elemento del menú. Si varios subprocesos intentan acceder al mismo bloque simultáneamente, debe imprimir el elemento del menú y el elemento de la lista en un orden diferente.

Utilice el método ReentrantLock

La clase ReentrantLock en Java es un método flexible para bloquear variables. La clase ReentrantLock proporciona sincronización al acceder a recursos o variables comunes.

Los métodos lock() y unlock() realizan el proceso. Un subproceso de Java obtiene el bloqueo a la vez. Otros subprocesos estarán en estado de bloqueo durante este tiempo.

Los hilos pueden entrar muchas veces a la cerradura. Por lo tanto, el nombre es Reentrante. Cuando el subproceso de Java obtiene el bloqueo, el recuento de espera es uno y el recuento se suma al volver a ingresar.

El recuento de retención de bloqueo disminuye en uno después de que se ejecuta unlock(). El ReentrantLock atiende los hilos en función del tiempo de espera.

El subproceso de Java con un tiempo de espera más largo tiene prioridad.

Para liberar el bloqueo en caso de algunas excepciones, el bloque lock() se ejecuta antes que el bloque try. El desbloquear() se ejecuta dentro del bloque finalmente.

las Funciones ReentrantLock

lock() Los incrementos mantienen la cuenta en uno. Si la variable está libre, asigne un bloqueo al subproceso de Java.
unlock() Los decrementos cuentan por uno. El bloqueo se libera cuando el conteo de retención es cero.
tryLock() Si el recurso es gratuito, esta función devuelve verdadero. De lo contrario, el hilo sale.
lockInterruptibly() Cuando un subproceso de Java utiliza el bloqueo, otros subprocesos de Java pueden interrumpir este subproceso de Java. Por lo tanto, el subproceso de Java actual tendrá que volver de inmediato.
getHoldCount() Devuelve el número de bloqueos en un recurso.
isHeldByCurrentThread() Cuando el subproceso Java actual utiliza el bloqueo, el método devuelve verdadero.

Utilice el Bloqueo de reentrada

En este programa, creamos un objeto para ReentrantLock. La clase ejecutable negocio se ejecuta y pasa el bloqueo al ReentrantLock.

Dos piscinas están disponibles para observar el resultado. El programa usa lock() para obtener el bloqueo y unlock() para finalmente liberar el bloqueo.

Los booleanos terminado y disponible realizan un seguimiento de la disponibilidad del bloqueo y la finalización de la tarea.

Se están ejecutando dos hilos. La segunda tienda no obtiene el candado y espera en la cola.

El taller 1 recibe primero la cerradura exterior y luego la cerradura interior. La tienda 2 está esperando en este momento.

El conteo de retención de bloqueo de Shop 1 es dos. El Taller 1 libera el candado interior y luego el exterior, y luego el conteo de retención del candado disminuye y el Taller 1 cierra.

La tienda 2 obtiene el candado automáticamente a través de la cola. El proceso se repite para la Tienda 2.

Código de ejemplo:

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 business implements Runnable {
  String name;
  ReentrantLock re;
  public business(ReentrantLock rl, String n) {
    re = rl;
    name = n;
  }
  public void run() {
    boolean finished = false;
    while (!finished) {
      // Get Outer Lock
      boolean isLockAvailable = re.tryLock();
      // If the lock is available
      if (isLockAvailable) {
        try {
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("Shop  - " + name + " got outside lock at " + ft.format(d));

          Thread.sleep(1500);
          // Get Inner Lock
          re.lock();
          try {
            d = new Date();
            ft = new SimpleDateFormat("hh:mm:ss");
            System.out.println("Shop - " + name + " got inside lock at " + ft.format(d));

            System.out.println("Lock Hold Count - " + re.getHoldCount());
            Thread.sleep(1500);
          } catch (InterruptedException e) {
            e.printStackTrace();
          } finally {
            // Inner lock release
            System.out.println("Shop - " + name + " releasing inside lock");

            re.unlock();
          }
          System.out.println("Lock Hold Count - " + re.getHoldCount());
          System.out.println("Shop - " + name + " closed");
          finished = true;
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          // Outer lock release
          System.out.println("Shop - " + name + " releasing outside lock");
          re.unlock();
          System.out.println("Lock Hold Count - " + re.getHoldCount());
        }
      } else {
        System.out.println("Shop - " + name + " waiting for lock");
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }
}
public class sample {
  static final int POOL_MAX = 2;
  public static void main(String[] args) {
    ReentrantLock rel = new ReentrantLock();
    ExecutorService pool = Executors.newFixedThreadPool(POOL_MAX);
    Runnable p1 = new business(rel, "Shop 1");
    Runnable p2 = new business(rel, "Shop 2");
    System.out.println("Running Pool 1");
    pool.execute(p1);
    System.out.println("Running Pool 2");
    pool.execute(p2);
    pool.shutdown();
  }
}

Producción :

Running Pool 1
Running Pool 2
Shop - Shop 2 waiting for the lock
Shop  - Shop 1 got the outside lock at 11:05:47
Shop - Shop 2 waiting for the lock
Shop - Shop 1 got the inside 'lock' at 11:05:48
Lock Hold Count - 2
Shop - Shop 2 waiting for the lock
Shop - Shop 2 waiting for the lock
Shop - Shop 1 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 1 closed
Shop - Shop 1 releasing the outside lock
Lock Hold Count - 0
Shop  - Shop 2 got the outside lock at 11:05:51
Shop - Shop 2 got the inside 'lock' at 11:05:52
Lock Hold Count - 2
Shop - Shop 2 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 2 closed
Shop - Shop 2 releasing the outside lock
Lock Hold Count - 0

Usar el semáforo binario o de conteo

Semaphore en Java decide la cantidad de subprocesos que acceden a un recurso juntos. Binary Semaphore otorga un pase o un permiso para acceder a un recurso en la programación de subprocesos múltiples.

Los subprocesos de Java deben esperar hasta que esté disponible un permiso de bloqueo. El método acquire() da el pase, y la función release() libera el pase de bloqueo.

La función adquirir() bloquea los hilos hasta que el permiso de bloqueo esté disponible.

Los dos estados disponibles en un Semáforo son permiso disponible y permiso no disponible.

En este ejemplo, un objeto Semaphore accede a la función commonResource(). Dos subprocesos se están ejecutando en el programa.

El método acquire() otorga un permiso a los subprocesos, y release() libera el permiso de bloqueo en función de la disponibilidad.

El subproceso 0 obtiene el pase y entra en el espacio ocupado. Luego, sale al espacio libre al liberar el permiso.

A continuación, el subproceso 1 obtiene el permiso de bloqueo, ingresa a la región crítica y llega al espacio libre después de la liberación del permiso.

Código de ejemplo:

import java.util.concurrent.Semaphore;
public class SemaphoreCounter {
  Semaphore binary = new Semaphore(1);
  public static void main(String args[]) {
    final SemaphoreCounter semObj = new SemaphoreCounter();
    new Thread() {
      @Override
      public void run() {
        semObj.commonResource();
      }
    }.start();
    new Thread() {
      @Override
      public void run() {
        semObj.commonResource();
      }
    }.start();
  }
  private void commonResource() {
    try {
      binary.acquire();
      // mutual sharing resource
      System.out.println(Thread.currentThread().getName() + " busy space");
    } catch (InterruptedException ie) {
      ie.printStackTrace();
    } finally {
      binary.release();
      System.out.println(Thread.currentThread().getName() + " free space");
    }
  }
}

Producción :

Thread-0 busy space
Thread-0 free space
Thread-1 busy space
Thread-1 free space

Usar la variable atómica

Las variables atómicas proporcionan sincronización en la programación de subprocesos múltiples de Java con sus funciones integradas.

El programa establece un límite de tiempo y un tamaño de subproceso. El executorService envía el temporizador haciendo un bucle hasta el límite del subproceso y cierra el servicio.

El objeto temporizador de la variable atómica aumenta el temporizador. Todo el proceso ocurre de forma sincronizada.

Cada subproceso de Java obtiene un recuento único sin interrupciones.

Código de ejemplo:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

class Timer implements Runnable {
  private static AtomicInteger timer;
  private static final int timerLimit = 10;
  private static final int totThreads = 5;
  public static void main(String[] args) {
    timer = new AtomicInteger(0);
    ExecutorService executorService = Executors.newFixedThreadPool(totThreads);
    for (int i = 0; i < totThreads; i++) {
      executorService.submit(new Timer());
    }
    executorService.shutdown();
  }
  @Override
  public void run() {
    while (timer.get() < timerLimit) {
      increaseTimer();
    }
  }
  private void increaseTimer() {
    System.out.println(Thread.currentThread().getName() + " : " + timer.getAndIncrement());
  }
}

Producción :

pool-1-thread-2 : 4
pool-1-thread-2 : 5
pool-1-thread-4 : 1
pool-1-thread-4 : 7
pool-1-thread-4 : 8
pool-1-thread-4 : 9
pool-1-thread-3 : 3
pool-1-thread-5 : 2
pool-1-thread-1 : 0
pool-1-thread-2 : 6

Este artículo nos enseñó cuatro métodos para sincronizar un recurso en Java en programación multiproceso. El método sincronizado es el método tradicional.

Optamos por el método ReentrantLock para aprovechar la flexibilidad. El ReentrantLock también tiene un inconveniente: el programador necesita realizar un seguimiento del bloque try-catch-finally al escribir lock() y unlock().

Aunque Binary Semaphore es similar al ReentrantLock, el ReentrantLock tiene solo el nivel básico de sincronización y mecanismo de bloqueo fijo. Binary Semaphore proporciona sincronización de alto nivel, bloqueo personalizado y prevención de puntos muertos.

Las variables atómicas también son un método para lograr la sincronización para actividades simples de lectura y escritura de datos.

Shubham Vora avatar Shubham Vora avatar

Shubham is a software developer interested in learning and writing about various technologies. He loves to help people by sharing vast knowledge about modern technologies via different platforms such as the DelftStack.com website.

LinkedIn GitHub

Artículo relacionado - Java Variable