[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









noxusrules dice:
Noviembre 2nd, 2008 a las 9:27 pm
Excelente articulo, yo estoy apenas entrandole a esto de la programacion y la neta hasta ahora que ya puedo programar un poco mas y por lo menos le entiendo a los codigos que leo en internet (que por cierto espero ser yo el que ponga codigos hehe) le estoy encontrando el gusto, tamben gracias por el articulo porque tengo que entregar un reporte sobre esto para la semana que viene, y esta pagina va a mis favoritos desde ya!!! Un saludo
Cristian dice:
Noviembre 4th, 2008 a las 10:20 am
Me alegra que te haya servido… no te olvides añadir este blog a tu lector de feeds!
Un saludo!
Denisse dice:
Noviembre 26th, 2008 a las 6:50 pm
como hago para hacerlo ejecutable en linux Red Hat 9.0 y ademas como se utiliza la clase Semaphore, del paquete java.util.concurrent ????
Cristian dice:
Noviembre 27th, 2008 a las 6:19 am
@Denisse: descargas los programas, los compilas asÃ:
javac *javaY lo ejecutas asÃ:
java UsoSemaforosUn saludo!
Denisse dice:
Noviembre 27th, 2008 a las 8:07 pm
Gracias por responderme.
ya compile el programa pero me salen errores en import java.util.concurrent por que no lo encuentra como hago para añadir esta libreria????
de antemano gracias
Denisse dice:
Noviembre 27th, 2008 a las 8:48 pm
Ah otra preguntita mas si no es molestia puedo adaptar el programas para 6 procesos que se ejecuten en orden?????
Cristian dice:
Noviembre 29th, 2008 a las 7:26 am
Por un lado… tal vez sea la versión de JDK. Con respecto a lo otro, por supuesto, puedes modificar el programa como deseas.
Un saludo!
chayito dice:
Diciembre 10th, 2008 a las 5:33 am
gracias!