Concepto de hilo.
Los hilos o threads, son básicamente, pequeños procesos o piezas
independientes de un gran proceso. También podemos decir, que un hilo es
un flujo único de ejecución dentro de un proceso (un proceso es un
programa ejecutándose dentro de su propio espacio de direcciones).
Un
hilo no puede correr por sí mismo, se ejecuta dentro de un programa, ya
que requieren la supervisión de un proceso padre para correr. Se
pueden porgramar múltiples hilos de ejecución para que corran
simultáneamente en el mismo programa. La utilidad de la programación
multihilo resulta evidente. Por ejemplo, un navegador Web puede
descargar un archivo de un sitio, y acceder a otro sitio al mismo
tiempo. Si el navegador puede realizar simultáneamente dos tareas, no
tendrá que esperar hasta que el archivo haya terminado de descargarse
para poder navegar a otro sitio.
Los hilos a menudo, son conocidos o llamados procesos ligeros.
Comparación.
Un thread o hilo es, al igual que un proceso, un flujo de control que
puede gozar de cierta autonomía (puede tener sus propias estructuras de
datos), pero a diferencia de un proceso, diversos hilos dentro de una
aplicación pueden compartir los mismos datos.
El beneficio de ser multihilo, consiste en un mejor rendimiento interactivo y un mejor comportamiento en tiempo real.
Atributos de hilos.
Los atributos o propiedades de un hilo varían de una implementación a otra. Sin embargo, de forma general los atributos que definen un thread son:
- Estado de espera: permite que otros hilos, esperen hasta que termine de ejecutarse un hilo en especial.
- Dirección de stack. apuntador al inicio del stock del hilo.
- Tamaño de la dirección: longitud del stock del hilo.
- Alcance (scope): define quien controla la ejecución del hilo: el proceso o el núcleo del sistema operativo.
- Herencia: los parámetros de calendarización son heredados o definidos localmente.
- Política de calendarización: se define que proceso se va a ejecutar y en qué instante.
- Prioridad: un valor de prioridad alto corresponde a una mayor prioridad.
Aunque el comportamiento en tiempo real, esta limitado a las
capacidades del sistema operativo sobre el que corre, aún supera a los
entornos de flujo único de programa (single-thread) tanto en facilidad
de desarrollo, como en rendimiento.
Mientras los procesos mantienen su propio espacio de direcciones y
entorno de operaciones, los hilos dependen de un programa padre en lo
que se refiere a recursos de ejecución.
En Java, los hilos comparten el mismo espacio de memoria. Incluso comparten gran parte del entorno de ejecución, de modo que la creación de nuevos hilos es mucho más rápida que la creación de nuevos procesos. La ventaja que proporcionan los hilos, es la capacidad de tener más de un camino de ejecución en un mismo programa.
Creación de hilos.
En Java, existen dos mecanismo que nos permiten la creación de hilos:
- Implementando la interfaz Runnable
- Extendiendo la clase Thread, es decir, creando una subclase de ésta.
En cualquiera de los dos casos, se debe definir un método run
que será el que incluya las instrucciones que se ejecutarán en el
thread (hilo) y se pueden definir prioridades aunque no se puede confiar
en que la máquina virtual escoja para ejecutar, siempre, el de mayor
prioridad, por lo que no se pueden utilizar para basar en ellas el
scheduler de un sistema en tiempo real.
La clase Thread.
class Repeticion extends Thread {
private int repeticiones;
private String mensaje;
Repeticion (String msg, int n) {
mensaje = msg;
repeticiones = n;
}
public void run() {
for (int i= 1; i <= repeticiones; i++)
System.out.println (mensaje + " " +i);
}
public static vouid main (String args [ ] {
Repeticion r1 = new Repeticion ("Rojo", 5);
Repeticion r2 = new Repeticion ("Azul", 80);
r1.start();
r2.start();
}
}
Cuando creamos un hilo extendiendo la clase Thread, se pueden heredar
los métodos y variables de la clase padre. Si es así, una misma subclase
solamente puede extender o drivar una vez la clase padre Thread. Esta
limitación de Java puede ser superada a través de la implementación de
Runnable que es una interfaz.
La interfaz Runnable.
class Repeticion2 implements Runnable {
private int repeticiones;
private String mensaje;
Repeticion2 (String msg, int n) {
mensaje = msg;
repeticiones = n;
}
public void run ( ) {
for (int i=1; i<= repeticiones; i++;)
System.out.println(mensaje + " " + i);
}
public static void main (String args [ ]) {
Repeticion r1 = new Repeticion ("Rojo", 5);
Thread r2 = new Thread (new Repeticion2 ("Azul", 80))
r1.start ();
r2.start ();
}
}
Arranque de hilos.
Como se pudo apreciar en los ejemplos anteriores, el arranque de un hilo debe realizarse dentro del método principal de Java, que como todos sabemos, es el método main. Y lo arrancamos llamando al método start.
r1.start ( );
start, es el método oculto en el hilo cuya función es llamar al método run.
Manipulación de hilos.
Una
vez que realizamos la creación de un hilo, éste debe contener una traza
de ejecución válida, la cual controlaremos en el método run del objeto.
El cuerpo de ésta función (las acciones del hilo), vienen a ser el
cuerpo del programa. Es como referirnos a la rutina main pero a nivel
del hilo. Es decir, todas las acciones que nos interesa que nuestro hilo
realice, deben estar especificadas en el método run. Al terminar de
ejecutarse el método run, también terminará la ejecución de nuestros
hilos.
Por lo anterior, la manipulación de nuestro hilos, se realiza dentro del método run.
Suspensión de hilos.
También podemos realizar la
suspensión de un hilo, es decir, detenerlo o desactivarlo por un
intervalo de tiempo indeterminado, para ésto utilizamos la función suspend.
Este método no detiene la
ejecución en forma permanente. El hilo es suspendido indefinidamente y
para volver a activarlo nuevamente es necesario realizar una invocación a
la función resume.
Es importante mencionar, que también existe la función sleep, pero en ésta se especifica el tiempo en milisegundos en el que el hilo permanecerá "dormido" y al término de éste tiempo el hilo continua ejecutándose.
Parada de hilos.
El método que
debemos utilizar para detener la ejecución de nuestro hilo, es stop, el
cual detendrá la ejecución en forma permanente.
t1.stop();
Este método no destruye el hilo, simplemente detiene su ejecución y ésta no puede ser reanudada con el método start.
Su utilidad tiene sentido, sobre
todo, en aplicaciones complejas que necesiten un control sobre cada uno
de los hilos que se ejecuten.
Sincronización de hilos.
La necesidad de la sincronización
de hilos, tiene lugar cuando varios hilos intentan acceder al mismo
recurso o dato. Es decir, los hilos necesitan establecer cierto orden, a
la hora de acceder a datos comunes. Para asegurarse de que los hilos
concurrentes no se estorban y operan correctamente con datos o recursos
compartidos, un sistema estable previene la inacición y el punto muerto o
interbloqueo. La inanición tiene lugar cuando uno o más hilos están
bloqueados al intentar conseguir el acceso a un recurso compartido de
ocurrencias limitadas. El interbloqueo es la última fase de la
inanición; ocurre cuando uno omás hilos están esperando una condición
que no puede ser satisfecha. Esto ocurre muy frecuentemente cuando dos o
más hilos están esperando a que el otro u otros desbloqueen algún dato u
objeto común.
Existen dos forma para aplicar la sincronización:
- Bloqueo de objetos
- Uso de señales.
Uso de semáforos o señales.
Dentro de éste sistema, un hilo puede detener su ejecución y esperar una señal de otro hilo para continuar con su ejecución.
En este sistema encontramos varios sistemas como son el uso de mutex, semáforos y barreras.
Semáforos.
En el caso de los semáforos,
podemos establecer un número máximo de hilos que pueden tener acceso
simultáneo a un recurso compartido en específico; es decir, es una
variable especial que constituye el método clásico para restringir o
permitir el acceso a recursos compartidos.
Cada vez que un hilo intenta
utilizar el recurso compartido, existe un contador que se va
decrementando en uno y lo deja pasar. En el momento en que el contador
se convierte en cero, deja bloqueado al hilo que intentó el acceso.
Inicia(Semáforo s, Entero v) { s = v; // Declara el contador de tipo entero }P(Semáforo s) { if(s>0) // Si aún no se ha excedido el número permitido deja pasar el hilo s = s-1; // Decrementa el contador else // Si ya se tiene el número permitido de hilos ejecutándose wait(); // Deja el hilo en espera }V(Semáforo s) { if(!procesos_bloqueados) s = s+1; else signal(); }Un tipo simple de semáforo es el binario, que puede tomar los valores de 0 y 1. Se inicializa en 1 y son usadoscuando solo un proceso puede acceder a un recurso a la vez.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:P2.java
01020304050607080910<spanclass="IL_AD"id="IL_AD8">public</span>classp1extendsThread {publicvoidrun() {try{<spanclass="IL_AD"id="IL_AD10">sleep</span>((int) Math.round(500* Math.random() -0.5));}catch(InterruptedException e) {e.printStackTrace();}System.out.println("P1");}}P3.java
01020304050607080910publicclassp2extendsThread {publicvoidrun() {try{sleep((int) Math.round(500* Math.random() -0.5));}catch(InterruptedException e) {e.printStackTrace();}System.out.println("P2");}}y P4.java
01020304050607080910publicclassp3extendsThread {publicvoidrun() {try{sleep((int) Math.round(500* Math.random() -0.5));}catch(InterruptedException e) {e.printStackTrace();}System.out.println("P3");}}y la clase SinSemaforos.java, que lanza los subprocesos:
01020304050607080910publicclassp4extendsThread {publicvoidrun() {try{sleep((int) Math.round(500* Math.random() -0.5));}catch(InterruptedException e) {e.printStackTrace();}System.out.println("P4");}}Ejecutando varias veces este programa, tendremos salidas como:
12345678publicclassSinSemaforos {publicstaticvoidmain(String[] args) {(newThread(newp1())).start();(newThread(newp2())).start();(newThread(newp3())).start();(newThread(newp4())).start();}}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!# java SinSemaforos P4 P2 P1 P3 # java SinSemaforos P2 P1 P3 P4 # java SinSemaforos P2 P1 P4 P3Solución usando semáforos
En este caso vamos a usar la clase Semaphore, del paquetejava.util.concurrent. A darle entonces: Tenemos entonces P1.java:P2.java
01020304050607080910111213141516importjava.util.concurrent.Semaphore;publicclassp1extendsThread {protectedSemaphore oFinP1;publicp1(Semaphore oFinP1) {this.oFinP1 = oFinP1;}publicvoidrun() {try{sleep((int) Math.round(500* Math.random() -0.5));}catch(InterruptedException e) {e.printStackTrace();}System.out.println("P1");this.oFinP1.release(2);}}P3.java
0102030405060708091011121314151617181920212223importjava.util.concurrent.Semaphore;publicclassp2extendsThread {protectedSemaphore oFinP1;protectedSemaphore oFinP3;publicp2(Semaphore oFinP1,Semaphore oFinP3) {this.oFinP3 = oFinP3;this.oFinP1 = oFinP1;}publicvoidrun() {try{this.oFinP1.<spanclass="IL_AD"id="IL_AD1">acquire</span>();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");}}y P4.java
01020304050607080910111213141516importjava.util.concurrent.Semaphore;publicclassp3extendsThread {protectedSemaphore oFinP3;publicp3(Semaphore oFinP3) {this.oFinP3 = oFinP3;}publicvoidrun() {try{sleep((int) Math.round(500* Math.random() -0.5));}catch(InterruptedException e) {e.printStackTrace();}System.out.println("P3");this.oFinP3.release(2);}}y la clase UsoSemaforos.java, que lanza los subprocesos:
0102030405060708091011121314151617181920212223importjava.util.concurrent.Semaphore;publicclassp4extendsThread {protectedSemaphore oFinP1;protectedSemaphore oFinP3;publicp4(Semaphore oFinP1,Semaphore oFinP3) {this.oFinP3 = oFinP3;this.oFinP1 = oFinP1;}publicvoidrun() {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");}}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:
010203040506070809101112importjava.util.concurrent.Semaphore;publicclassUsoSemaforos {protectedstaticSemaphore oFinP1,oFinP3;publicstaticvoidmain(String[] args) {oFinP1 =newSemaphore(0,true);oFinP3 =newSemaphore(0,true);(newThread(newp1(oFinP1))).start();(newThread(newp2(oFinP1,oFinP3))).start();(newThread(newp3(oFinP3))).start();(newThread(newp4(oFinP1,oFinP3))).start();}}#java UsoSemaforos P3 P1 P2 P4 #java UsoSemaforos P1 P3 P2 P4 #java UsoSemaforos P3 P1 P4 P2 #java UsoSemaforos P1 P3 P4 P2Conclusiones…
- Lo primero es el método
acquire()de la clase Semaphore. Este método bloquea el semáforo premanetemente (wait); mientras que, - el método
release()de la clase Semaphore, libera el semáforo para los demás procesos (signal).



No hay comentarios:
Publicar un comentario