Anteriormente hemos visto:
Ahora veremos:
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:
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:
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:
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.
La imagen de la salida del código de Viaje sería la siguiente: