[código] Programación concurrente: uso de semáforos
Hablamamos en anteriores entradas acerca de la programación multihilo o concurrente en Java, e hicimos algunos ejemplos de sincronización. De nuevo veremos algo de subprocesamiento múltiple, esta vez explicando el uso de los semáforos. Primero veamos algo de teoría:
Un semáforo binario es un indicador (S) de condición que registra si un recurso está disponible o no. Un semáforo binario sólo puede tomar dos valores: 0 y 1. Si, para un semáforo binario, S = 1 entonces el recurso está disponible y la tarea lo puede utilizar; si S = 0 el recurso no está disponible y el proceso debe esperar.
Los semáforos se implementan con una cola de tareas o de condición a la cual se añaden los procesos que están en espera del recurso. Sólo se permiten tres operaciones sobre un semáforo:
- Inicializar
- Espera (wait)
- Señal (signal)
En algunos textos, se utilizan las notaciones P y V para las operaciones de espera y señal respectivamente, ya que ésta fue la notación empleada originalmente por Dijkstra (el creador de la solución) para referirse a las operaciones.
Lo importante aquí es entender de verdad cómo funcionan los semáforos en Java. Por eso, basta de práctica y vamos al código. Aún así, sería bueno que le echaras un ojo a los siguientes documentos de referencia:
Problemas al NO usar semáforos
Lo primero es ver una aplicación fictica corriendo SIN el uso de los semáforos, para luego entender mejor su utilidad. Su pongamos que tenemos 4 procesos (p1, p2, p3, p4), cada proceso realiza su “tarea” simultaneamente (durante un tiempo indefinido) y posteriormente termina. Supongamos además que necesitamos que se ejecuten primero los procesos P1 y P3, y luego P2 y P4.
Tenemos entonces P1.java:
public class p1 extends Thread {
public void run() {
try {
sleep((int) Math.round(500 * Math.random() - 0.5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("P1");
}
}
P2.java
public class p2 extends Thread {
public void run() {
try {
sleep((int) Math.round(500 * Math.random() - 0.5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("P2");
}
}
P3.java
public class p3 extends Thread {
public void run() {
try {
sleep((int) Math.round(500 * Math.random() - 0.5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("P3");
}
}
y P4.java
public class p4 extends Thread {
public void run() {
try {
sleep((int) Math.round(500 * Math.random() - 0.5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("P4");
}
}
y la clase SinSemaforos.java, que lanza los subprocesos:
public class SinSemaforos {
public static void main(String[] args) {
(new Thread(new p1())).start();
(new Thread(new p2())).start();
(new Thread(new p3())).start();
(new Thread(new p4())).start();
}
}
Ejecutando varias veces este programa, tendremos salidas como:
# java SinSemaforos
P4
P2
P1
P3
# java SinSemaforos
P2
P1
P3
P4
# java SinSemaforos
P2
P1
P4
P3
Como puedes ver, los procesos se ejecutan sin cumplir la condición más importante: que se ejecuten primero los procesos P1 y P3, y posteriormente P2 y P4. Solucionemos esto con el uso de semáforos!
Solución usando semáforos
En este caso vamos a usar la clase Semaphore, del paquete java.util.concurrent. A darle entonces:
Tenemos entonces P1.java:
import java.util.concurrent.Semaphore;
public class p1 extends Thread {
protected Semaphore oFinP1;
public p1(Semaphore oFinP1) {
this.oFinP1 = oFinP1;
}
public void run() {
try {
sleep((int) Math.round(500 * Math.random() - 0.5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("P1");
this.oFinP1.release(2);
}
}
P2.java
import java.util.concurrent.Semaphore;
public class p2 extends Thread {
protected Semaphore oFinP1;
protected Semaphore oFinP3;
public p2(Semaphore oFinP1,Semaphore oFinP3) {
this.oFinP3 = oFinP3;
this.oFinP1 = oFinP1;
}
public void run() {
try {
this.oFinP1.acquire();
this.oFinP3.acquire();
} catch(Exception e) {
e.printStackTrace();
}
try {
sleep((int) Math.round(500 * Math.random() - 0.5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("P2");
}
}
P3.java
import java.util.concurrent.Semaphore;
public class p3 extends Thread {
protected Semaphore oFinP3;
public p3(Semaphore oFinP3) {
this.oFinP3 = oFinP3;
}
public void run() {
try {
sleep((int) Math.round(500 * Math.random() - 0.5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("P3");
this.oFinP3.release(2);
}
}
y P4.java
import java.util.concurrent.Semaphore;
public class p4 extends Thread {
protected Semaphore oFinP1;
protected Semaphore oFinP3;
public p4(Semaphore oFinP1,Semaphore oFinP3) {
this.oFinP3 = oFinP3;
this.oFinP1 = oFinP1;
}
public void run() {
try {
this.oFinP1.acquire();
this.oFinP3.acquire();
} catch(Exception e) {
e.printStackTrace();
}
try {
sleep((int) Math.round(500 * Math.random() - 0.5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("P4");
}
}
y la clase UsoSemaforos.java, que lanza los subprocesos:
import java.util.concurrent.Semaphore;
public class UsoSemaforos {
protected static Semaphore oFinP1,oFinP3;
public static void main(String[] args) {
oFinP1 = new Semaphore(0,true);
oFinP3 = new Semaphore(0,true);
(new Thread(new p1(oFinP1))).start();
(new Thread(new p2(oFinP1,oFinP3))).start();
(new Thread(new p3(oFinP3))).start();
(new Thread(new p4(oFinP1,oFinP3))).start();
}
}
Ejecutando varias veces el programa, podemos ver como los subprocesos P1 y P3 se ejecutan siempre de primeras, y los procesos P2 y P4, de ultimas:
#java UsoSemaforos
P3
P1
P2
P4
#java UsoSemaforos
P1
P3
P2
P4
#java UsoSemaforos
P3
P1
P4
P2
#java UsoSemaforos
P1
P3
P4
P2
Conclusiones…
- Lo primero es el método
acquire()de la claseSemaphore. Este método bloquea el semáforo premanetemente (wait); mientras que, - el método
release()de la claseSemaphore, libera el semáforo para los demás procesos (signal).
Descargar ejemplos:
- Ejemplo de programación concurrente, SIN semáforos
- Ejemplo de programación concurrente, usando semáforos
23 Comentarios | deja el tuyo



¡Gracias!
Hola,
Cómo podría hacer un .acquire que esperara como máximo un timeout.
Mi problema es que puede que el thread acabe y haga el signal antes que el que hace wait, entonces se queda esperando eternamente.
Muchísimas gracias!!
Podrais ayudarme a que los procesos vayan en orden P1 P2 P3 P4.. etc(los que sean necesarios) y darle un tiempo de acceso en su region critica y que tenga modo grafico en linux.. te lo agradeceria muchisimo, ya que recien estoy empezando a programar en java
gracias por tu aporte
Esta muy bueno este ejemplo, me va a servir mucho en mi clase, gracias.
“Estudiantes”
talves yo no escribi este tema, mas sin embargo te puedo asegurar que es sumamente dificil darle orden a los hilos para que se ejecuten como tu quieras, en este caso como dices P1 P2 P3 P4…, por que realmente el que decide es el calendarizador de la maquina virtual de java, a pesar de que tu le pongas priioridad a los hilos no es garantia de que se ejecuten primero antes que otros hilos de menor prioridad.