6

Uso de hilos en Java(parte 6)

Anteriormente hemos visto:

  • Ejecución secuencial.
  • Uso de concurrencia heredando de la clase Thread e implementar la interfaz Runnable.
  • Particionamiento del trabajo entre los hilos.
  • Comparación del tiempo de la ejecución secuencial vs la ejecución paralela.
  • Ejecutores : Explicación, tipos, ventajas y desventajas.
  • Ejemplo de uso de ejecutores.
  • Entrelazado de instrucciones, sus consecuencias y código de ejemplo de entrelazado.
  • Solución e implementación de la solución al problema.
  • Explicación de synchronized, relación con los cerrojos y ejemplo de cerrojos.
  • Tipos de datos atómicos.

Ahora veremos:

  • Control de acceso de hilos usando semáforos.
  • Control de sincronización a través del uso de barreras.

En los ejemplos anteriores he puesto varias situaciones en las que solo un recurso se modificaba por varios hilos, en este caso vamos a suponer que tenemos más de un recurso, imagina una tienda de ropa en la que tenemos 5 probadores y tenemos 20 clientes(hilos) que desean entrar a esos probadores y tomarse su tiempo decidiendo si comprar la ropa o no, al tener 5 probadores nos interesa que se utilicen por el mayor número de clientes(hilos) a la vez. Esto ya supone un cambio en la forma de acceso de los hilos ya que queremos que varios accedan a la vez al recurso compartido, aqui es donde entra en acción los semáforos, definiremos los semáforos como una clase de objetos que gestionarán el acceso a los recursos a través de los permisos disponibles, en nuestro caso queremos tener 5 permisos disponibles ya que podemos tener hasta un máximo de 5 clientes usando los probadores, las operaciones que utilizarán los semáforos serán las siguientes:

  • Adquirir permiso/acquire() : Si el número de permisos disponibles es mayor que cero entonces permitirá el acceso a un hilo y decrementa el número de permisos en 1 unidad, en caso de ser igual a 0 pondrá al cliente(hilo) en espera hasta que haya un permiso disponible para este.
  • Liberar permiso/release() : Cuando un cliente(hilo) usa este método significa que deja un probador(permiso) disponible por lo que incrementamos el número de permisos en 1 unidad, si hay un cliente esperando entonces este cliente adquirirá el permiso(un cliente aleatorio).

Entonces podemos modelar los 5 probadores usando un semáforo de 5 permisos, los clientes que no estén dentro de un probador tendrán que adquirir un permiso o esperar hasta que haya un permiso disponble, para el uso de semáforos emplearemos la clase Semaphore que implementa Java y se comporta de la forma anteriormente descrita, veamos el código que define esta situación:
Clase Tienda

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

publicclassTienda {

  publicvoidusarProbador(int idClliente) {
    long tiempoNecesitado = (long) (Math.random() * 10000);
    try {
      System.out.println("El cliente " + idClliente + " acaba de entrar a un probador");
      Thread.sleep(tiempoNecesitado); // El cliente se tomara su tiempo para probarse la ropa.
      System.out.println("El cliente " + idClliente + " ha terminado en un tiempo " + tiempoNecesitado);
    } catch (InterruptedException E) {
      System.out.println("Se genero una excepcion probandose ropa");
    }
  }

  publicstaticvoidmain(String[] args) {
    Tienda tienda = new Tienda(); // Usaremos la misma referencia para que todos accedan a la misma tienda.
    ExecutorService ejecutor = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 20; i++) {
      ejecutor.execute(new Cliente(i, tienda));
    }
    ejecutor.shutdown();
    while (!ejecutor.isTerminated())
      ;
  }
}

Esta sería la clase Cliente

import java.util.concurrent.Semaphore;

publicclassClienteimplementsRunnable{
  int idCliente;
  Tienda tienda;
  staticint numeroPermisos = 5; // Numero de probadores.static Semaphore semaforo = new Semaphore(numeroPermisos);

  publicCliente(int id, Tienda shop){
    this.idCliente = id;
    this.tienda = shop;
  }

  publicvoidrun(){
    try {
      semaforo.acquire();
      this.tienda.usarProbador(this.idCliente);
    } catch (InterruptedException E) {
    }
    semaforo.release();
  }

}

Tras ejecutar el código obtenemos la siguiente salida:
Semaforo.png
Los semáforos nos permiten modelar una gran variedad de situaciones en las que tenemos más de un recurso compartido y queremos que sean utilizados por más de un hilo de forma simultánea.
Hablemos ahora de sincronización entre hilos, imagina que queremos que 4 personas que están situadas en diferentes ciudades realicen un viaje, que vayan a un punto B tras haberse encontrado los 4 en un punto A primeramente, lo más probable es que alguno llegue antes que otro, los que ya hayan llegado tendrán que esperar en un punto a que lleguen los restantes, usaremos un tipo de dato llamado barrera que se encargará de controlar la espera en el punto A, definimos el comportamiento de la barrera se obtiene a partir del número de personas a esperar(bloqueos) y los siguientes métodos:

  • Esperar/await() : Si el número de bloqueos disponibles es superior a 0 entonces la persona(hilo) consumirá un bloqueo y decrementará el número disponible además de esperar a que el número de bloqueos llegue a 0,
  • Reset() : Si alguien usa este método la barrera volverá a tener el número de permisos original, este método no se suele utilizar ya que la implementación de Java lo empleará de forma implícita cuando el número de permisos llegue a 0 y haya dejado pasar a los a hilos bloqueados.

Para el uso de barreras utilizaremos la clase CyclicBarrier que trae implementada Java, veamos la situación anterior en el código:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Viaje extends Thread {
  static int numeroBloqueos = 4;
  static CyclicBarrier barrera = new CyclicBarrier(numeroBloqueos);

  public void run() {
    System.out.println("Ya he llegado al punto A, esperare a que estemos los 4 juntos");
    try {
      barrera.await();
    } catch (InterruptedException | BrokenBarrierException B) {
    }
    puntoB();
  }

  public void puntoB() {
    System.out.println("Ya hemos llegado todos al punto B");
  }

  public static void main(String[] args) throws InterruptedException {
    Viaje[] hilos = new Viaje[4];for (int i = 0; i < 4; i++) {
      hilos[i] = new Viaje();
    }
    for (int i = 0; i < 4; i++) {
      hilos[i].start(); // Para simular la espera vamos a poner un sleep entre los starts.
      Thread.sleep(1000L);
    }
    for (int i = 0; i < 4; i++) {
      hilos[i].join();
    }
  }
}

Tras ejecutar el código se aprecia como llegan los hilos al punto de la barrera y realizan la espera.

PD : Para ejecutar los códigos los sitúo en el mismo directorio y uso javac Clase.java con las clases que intervienen, luego se usa java ClaseConElMain y se ejecutaría el código.

Escribe tu comentario
+ 2
1
13092Puntos
5 años

La imagen de la salida del código de Viaje sería la siguiente:
Viaje.png