Threads

9
CAPÍTULO 2 PROGRAMACIÓN CONCURRENTE: THREADS 2.1 INTRODUCCIÓN Todos los programas Java que hemos visto hasta ahora se ejecutan de manera independiente a otros programas Java que podamos tener en nuestro sistema. Si quisiéramos, por ejemplo, desarrollar una aplicación Telefono, tendríamos que programar estas dos actividades: Recoger sonidos con un micrófono y enviarlos en tiempo real a otro ordenador. Recibir los sonidos que nos llegan desde otro ordenador y reproducirlos por los altavoces. Si queremos poder hablar y escuchar lo que nos dicen, de manera simultánea, utilizando los conocimientos adquiridos hasta el momento solo tenemos una opción: abrir dos consolas en nuestro sistema y ejecutar cada aplicación (la que envía sonido y la que recibe sonido) en cada consola. La solución adoptada aprovecha la capacidad que tienen los sistemas operativos para ejecutar varios procesos de manera simultánea (paralela, concurrente); en este caso, el sistema operativo ejecuta en paralelo cada una de nuestras máquinas virtuales Java (JVM), que se encuentran en las dos consolas abiertas. Si quisiéramos darle un uso profesional a nuestra aplicación, enseguida descubriríamos que no es factible “vender” una solución que requiere ejecutar distintas partes del programa por separado. Lo adecuado es que una misma aplicación Java pueda ejecutar varios procesos concurrentemente. Los Threads (hilos) de Java permiten ejecutar en paralelo varios programas que se encuentran en la misma aplicación. En el ejemplo Telefono , haciendo uso de threads, podemos ejecutar simultáneamente el programa que captura y envía sonidos

description

programacion concurrente

Transcript of Threads

  • CAPTULO 2

    PROGRAMACIN CONCURRENTE:

    THREADS

    2.1 INTRODUCCIN

    Todos los programas Java que hemos visto hasta ahora se ejecutan de manera independiente a otros programas Java que podamos tener en nuestro sistema. Si quisiramos, por ejemplo, desarrollar una aplicacin Telefono, tendramos que programar estas dos actividades:

    Recoger sonidos con un micrfono y enviarlos en tiempo real a otro ordenador.

    Recibir los sonidos que nos llegan desde otro ordenador y reproducirlos por los altavoces.

    Si queremos poder hablar y escuchar lo que nos dicen, de manera simultnea, utilizando los conocimientos adquiridos hasta el momento solo tenemos una opcin: abrir dos consolas en nuestro sistema y ejecutar cada aplicacin (la que enva sonido y la que recibe sonido) en cada consola.

    La solucin adoptada aprovecha la capacidad que tienen los sistemas operativos para ejecutar varios procesos de manera simultnea (paralela, concurrente); en este caso, el sistema operativo ejecuta en paralelo cada una de nuestras mquinas virtuales Java (JVM), que se encuentran en las dos consolas abiertas.

    Si quisiramos darle un uso profesional a nuestra aplicacin, enseguida descubriramos que no es factible vender una solucin que requiere ejecutar distintas partes del programa por separado. Lo adecuado es que una misma aplicacin Java pueda ejecutar varios procesos concurrentemente.

    Los Threads (hilos) de Java permiten ejecutar en paralelo varios programas que se encuentran en la misma aplicacin. En el ejemplo Telefono, haciendo uso de threads, podemos ejecutar simultneamente el programa que captura y enva sonidos

  • 14 JESS BOBADILLA SANCHO

    con el programa que los recibe y los manda a los altavoces; de esta manera, podemos ejecutar toda la funcionalidad desde una sola consola.

    2.2 DEFINICIN DE PROGRAMAS CONCURRENTES

    Existen dos formas de definir una clase para que pueda ejecutarse en paralelo a otras clases:

    Extendiendo la clase Thread Implementando el interfaz Runnable

    Ejemplos: public class SeEjecutaConcurrentemente extends Thread

    public class SeEjecutaConcurrentemente implements Runnable

    Puesto que Java no soporta herencia mltiple, si necesitamos heredar los miembros de una clase resulta necesario hacer uso del interfaz Runnable; por ejemplo, si deseamos crear un applet que se ejecute concurrentemente con otros programas, podemos definir nuestra clase como:

    public class AppletConcurrente extends java.applet.Applet implements Runnable

    El interfaz Runnable es extremadamente sencillo: nicamente contiene el mtodo run(). Cuando un objeto que implementa este interfaz se usa para crear un thread, al arrancar el thread se ejecuta automticamente el mtodo run.

    La clase Thread implementa el interfaz Runnable y contiene un variado grupo de constructores y mtodos que permiten definir el comportamiento y evolucin de los programas concurrentes.

    Las estructuras ms simples de programas que usan threads son:

    Empleando la clase Tread: 1 class TelefonoEnvia extends Thread { 2 .... // propiedades, constructores y mtodos de la clase 3 public void run() { 4 // recoge sonidos del microfono y los enva por la //red 5 } 6 }

  • JESS BOBADILLA SANCHO 15

    Para crear y arrancar un thread con el comportamiento de TelefonoEnvia : 1 TelefonoEnvia InstanciaEnvia = new TelefonoEnvia(); 2 InstanciaEnvia.start();

    Empleando el interfaz Runnable : 1 class TelefonoEnvia implements Runnable { 2 .... // propiedades, constructores y mtodos de la 3 .... // clase 4 public void run() { 5 // recoge sonidos del microfono y los enva por la red 6 } 7 }

    Para crear y arrancar un thread con el comportamiento de TelefonoEnvia : 1 TelefonoEnvia InstanciaEnvia = new TelefonoEnvia(); 2 new Thread(InstanciaEnvia).start();

    Como se puede apreciar en las estructuras de programa mostradas, para arrancar un thread debemos utilizar el mtodo start, que a su vez provoca la ejecucin de l mtodo run. En la ltima lnea de cdigo mostrada, se utiliza un constructor de la clase Thread que admite un objeto Runnable como parmetro.

    Un mtodo importante de la clase Thread es: static void sleep (long milisegundos) // detiene la ejecucin del thread al

    // menos el nmero de milisegundos indicado

    2.3 PRIMER EJEMPLO

    Con los conceptos explicados hasta el momento estamos en condiciones de afrontar nuestro primer ejemplo, que hace uso de dos threads: cada thread imprime una frase en momentos diferentes.

    La clase ThreadBasico (lnea 3) implementa el interfaz Runnable, y por tanto su nico mtodo run (lnea 13). Cada instancia de la clase ThreadBasico incorpora las propiedades Frase y Aleatorio, que contendrn el String a imprimir y una medida del tiempo que se tardar en hacerlo.

  • 16 JESS BOBADILLA SANCHO

    El String a imprimir le llega a la clase a travs de su constructor (lnea 8). La propiedad Aleatorio contiene el valor numrico aleatorio que le proporciona la clase Random (lnea 10).

    El mtodo run (que se invoca automticamente tras el mtodo start) imprime la frase por consola (lnea 16) y se duerme durante un tiempo aleatorio (lnea 17). Estas acciones se repiten indefinidamente en un bucle (lneas 15 y 19). El bloque try-catch resulta necesario para contemplar el caso de que nuestro thread sea interrumpido. 1 import java.util.Random; 2 3 public class ThreadBasico implements Runnable { 4 5 private String Frase; 6 private Random Aleatorio; 7 8 public ThreadBasico (String Frase) { 9 this.Frase = Frase; 10 Aleatorio = new Random(); 11 } // Constructor 12 13 public void run() { 14 try { 15 do { 16 System.out.println (Frase); 17 Thread.sleep( (long) (Math.abs(Aleatorio.nextInt()) 18 % 500)); 19 } while (true); 20 } catch (InterruptedException e) {} 21 } // run 22 23 } // class

    Para probar el comportamiento de la clase ThreadBasico se proporciona la clase ThreadBasicoMain, donde se crean dos instancias de ThreadBasico (lneas 4 a 7). Cada instancia recibe una palabra de la consola a travs del parmetro de tipo String[] del mtodo main; de esta manera, tecleando en consola el comando de ejecucin: java ThreadBasicoMain uno dos, en args[0] recogemos el valor uno y en args[1] el valor dos.

    En las lneas 10 y 11 se arrancan los dos threads instanciados, lo que provoca la ejecucin del mtodo run de ambas instancias. El resultado de una ejecucin concreta se muestra a continuacin del cdigo de la clase. 1 public class ThreadBasicoMain { 2

  • JESS BOBADILLA SANCHO 17

    3 public static void main(String[] args) { 4 Thread PrimerHilo = new Thread 5 (new ThreadBasico (args[0])); 6 Thread SegundoHilo = new Thread 7 (new ThreadBasico (args[1])); 8 9 10 PrimerHilo.start(); 11 SegundoHilo.start(); 12 13 } 14 }

    2.4 EJEMPLO CON UN NMERO VARIABLE DE HILOS (THREADS)

    El ejemplo que se desarrolla en este apartado es bastante similar al anterior, aunque en este caso el nmero de hilos que creamos vara dependiendo de los datos de entrada.

    La aplicacin recoge una palabra que se le pasa como argumento y escribe cada una de sus letras en un orden aleatorio; para conseguir este efecto se asigna un hilo a cada letra (o carcter), se detiene cada hilo durante un tiempo aleatorio y posteriormente se impr ime el carcter. La estructura de la aplicacin presenta las siguientes dependencias:

    CHilo

    LetrasHilos

    PruebaLetrasHilos

  • 18 JESS BOBADILLA SANCHO

    Chilo implementa el interfaz Runnable . En su constructor (lnea 6) recoge un String de tamao uno. Cuando se activa el hilo (thread) en primer lugar se le duerme durante un tiempo aleatorio (lneas 12 y 13) y posteriormente se imprime el carcter (lnea 14). 1 import java.util.Random; 2 3 public class CHilo implements Runnable { 4 private String Caracter; 5 6 public CHilo (String Caracter) { 7 this.Caracter = Caracter; 8 } 9 10 public void run() { 11 try { 12 Thread.sleep( (long) 13 (Math.abs(new Random().nextInt())%1000)); 14 System.out.print(Caracter); 15 } catch (InterruptedException e) {} 16 } 17 18 }

    La clase LetrasHilos define un array de threads (lnea 4) y posteriormente itera en un bucle tantas veces como posiciones tiene el String que recoge como parmetro (lnea 5). En cada iteracin se crea un thread utilizando la clase CHilo y se le pasa como argumento un carcter del String (lneas 6 y 7). En la lnea 8 se arranca el hilo.

    Aunque el array de hilos no es necesario en el ejemplo, resulta adecuado mostrar la manera de crear estas estructuras de hilos, puesto que en otras ocasiones son muy tiles. 1 public class LetrasHilos { 2 3 public LetrasHilos (String Frase) { 4 Thread[] Hilo = new Thread[Frase.length()]; 5 for (int i=0; i!=Frase.length();i++) { 6 Hilo[i] = new Thread 7 (new CHilo(Frase.substring(i,i+1))); 8 Hilo[i].start(); 9 } 10 return; 11 } 12 }

  • JESS BOBADILLA SANCHO 19

    El ejemplo se completa proporcionando una clase que contenga el mtodo main e instancie a la clase LetrasHilos, pasando un String como argumento. Esto se realiza en la lnea 8 de la clase PruebaLetrasHilos. 1 public class PruebaLetrasHilos { 2 3 public static void main(String[] args) { 4 if (args.length!=1) { 5 System.out.println("Hay que introducir un argumento"); 6 System.exit(0); 7 } 8 LetrasHilos Instancia = new LetrasHilos(args[0]); 9 } 10 11 }

    En el siguiente grfico se muestra la consola del sistema con varias ejecuciones de la clase PruebaLetrasHilos; en cada una de ellas el orden de las letras es diferente, como cabe esperar por los tiempos aleatorios en los que se despiertan las distintas instancias de la clase CHilo.

    2.5 EXCLUSIN MUTUA (MODIFICADOR SYNCHRONIZED)

    Cuando se programa haciendo uso de concurrencia (paralelismo) hay que tener en cuenta la necesidad de establecer, en ocasiones, zonas de exclusin en el acceso a los recursos. Pongamos un ejemplo de esta situacin: para controlar el paso a un tnel de un solo carril, en ambos lados del tnel se colocan semforos. Cuando un automvil encuentra el semforo en verde, se le da permiso de entrada e inmediatamente se pone el semforo en rojo, hasta que sale del tnel. El cdigo quedara como: 1 ..... 2 if (Semaforo.equals(Verde)) { 3 PermisoPaso();

  • 20 JESS BOBADILLA SANCHO

    4 Semaforo=Rojo 5 } 6 .......................

    Si dos o ms threads (que se ejecutan en paralelo) leen simultneamente el valor de la propiedad Semaforo y determinan que est en verde, esos threads ejecutarn al mismo tiempo el mtodo PermisoPaso, produciendo una situacin errnea (y en este caso muy peligrosa).

    Para evitar situaciones como la ilustrada podemos proteger el acceso a los recursos (en nuestro ejemplo el recurso abstracto es el tnel y el recurso concreto en la variable Semaforo). Java permite definir mtodos de exclusin mutua, donde slo puede ejecutarse un hilo en cada momento.

    Colocando el modificador synchronized en un mtodo conseguimos que su ejecucin sea exclusiva, es decir, que no podr haber dos o ms hilos ejecutando simultneamente ese mtodo.

    El mtodo PeticionEntradaATunel, al incluir el modificador synchronized, resuelve el problema de exclusin mutua que hemos planteado. 1 public synchronized boolean PeticionEntradaATunel(){ 2 ..... 3 if (Semaforo.equals(Verde)) { 4 PermisoPaso(); 5 Semaforo=Rojo 6 } 7 ....................... 8 }

    2.6 MTODOS MS IMPORTANTES DE LA CLASE THREAD

    Adems de los dos constructores ms utilizados de esta clase: Thread() y Thread(Runnable destino)

    existe una amplia coleccin de mtodos destinados, en su mayora, a controlar la ejecucin de los hilos. La siguiente tabla muestra los mtodos ms tiles y comnmente utilizados:

  • JESS BOBADILLA SANCHO 21

    Mtodo Accin

    void start() Provoca el comienzo de la ejecucin del hilo. void run() Se ejecuta automticamente tras start, cuando el hilo se ha

    construido a partir del interfaz Runnable (o la clase Thread).

    static void sleep(long ms) Detiene la ejecucin del hilo durante, al menos, los milisegundos indicados

    void destroy() Elimina el hilo sin liberara recursos void setPriority(int Prioridad) Establece una prioridad para el hilo (MAX_PRIORITY,

    MIN_PRIORITY, NORM_PRIORITY) int getPriority() Indica la prioridad del hilo final void join() Espera a que este hilo termine static Thread currentThread() Devuelve un apuntador al hilo que se est ejecutando boolean isAlive() Indica si el hilo ha sido arrancado y todava no ha

    terminado void setDaemon(boolean on) Establece el hilo como demonio o como usuario. Los hilos

    demonio estn supeditados a los hilos que los han creado, de tal manera que cuando el creador termina, sus hijos demonio tambin finalizan

    public final bolean isDaemon() Indica si el hilo es de tipo demonio o usuario void setName(String name) Asigna un nombre al hilo String getName() Devuelve el nombre del hilo