Download Programación de Hilos
Document related concepts
no text concepts found
Transcript
Programación de Hilos 12 - 1 CAPITULO 12 Programación de Hilos Objetivos • Utilizar la clase Timer para manejo de procesos que requieran ser controlados según intervalos de tiempo dados. • Caracterizar aplicaciones que manejen multiples tareas • Desarrollar aplicaciones que permitan manejar animaciones. • Utilizar la clase Thread y la interface Runnable para el manejo de hilos desde un programa. • Indicar los elementos necesarios para el manejo de la sincronización de procesos • Introducción En la naturaleza se presentan innumerables ejemplos de procesos que se realizan de manera parela, simultáneos o concurrentes. Por ejemplo, para el caso del lector, los procesos que está realizando todo su cuerpo integral, mientras realiza la acción consciente, de leer este párrafo. En el plano de los sistemas artificiales, la concurrencia es un denominador común, el computador, la internet, los juegos, en los sistemas sofisticados de los automóviles modernos, las actuales comunicaciones inalambricas, etc. Por tales razones, la concurrencia es un fenómeno importante de tratar, ya que permite el desarrollo de sistemas capaces de adelantar, múltiples procesos simultáneamente. A su vez cada proceso se puede componer de múltiples Programación de Hilos: Introducción 12 - 2 tareas, ejecutándose según ciertas reglas, de manera simultánea. Debe entenderse que, se hace referencia a procesos en el sentido de programas, y dentro de cualquier programa, la posibilidad de manejar, concurrentemente, múltiples tareas. Hasta el momento se han desarrollado programas que realizan una sola tarea, entendiendose por tarea, un hilo de ejecución o un thread, tambien denominados de flujo único, en contraste con los programas que realizan más de una tarea, o de multitareas o multithreads, denominados de flujo múltiple. Especificamente una tarea se encarga de controlar un único aspecto, dentro de la ejecución de un programa, por ejemplo el manejo de gráficos, las entradas/salidas de archivos en disco, u otros. Las tareas se diferencian de los procesos, en que las primeras comparten los mismos recursos del programa que las contiene, en tanto los procesos tienen en forma separada su código, así como sus datos. Flujo único: un programa de flujo único (single-Thread) utiliza un solo hilo para controlar su ejecución. Por defecto, cualquier programa applet o aplicación Java, tiene un hilo denominado principal (Thread). En estos programas este hilo no necesita explicitarse, pues por defecto son de flujo único. Realizan las operaciones indicadas en un solo hilo o única tarea y terminan. En otros terminos, un programa en ejecución en Java es un hilo. Flujo múltiple: un programa de flujo múltiple o multitarea, utiliza varios contextos de ejecución para realizar su trabajo. Cada tarea se inicia y termina tan pronto como sea posible, lo cual es una facilidad para la entrada de datos en sistemas en tiempo real, especialmente si estos son de diferentes fuentes. En este caso, se dice que ademas del hilo principal del programa en ejección, este tiene otros hilos, o tareas parelas en ejecución. Se puede definir hilo como una secuencia única de control de flujo dentro de un programa. Entendiendo que puede haber más de una secuencia de control o hilos. Java permite realizar programación multihilo, el cual consiste en programas que contienen dos o más partes que se ejecutan de manera concurrente. Una parte que se ejecuta independientemente de las demás se denomina un hilo (thread). En un entorno de multitarea basada en hilos, el hilo es la unidad de código más pequeña que se puede seleccionar para ejecución. La programación basada en multihilos, permite escribir programas más eficientes ya que optimizan los recursos de la Unidad Central de Proceso (CPU), al reducir al mínimo los tiempos de inactividad. Este es un factor muy importante a tener en cuenta en el manejo de entornos interactivos, como es el caso del trabajo en la red, en donde Programación de Hilos: Introducción 12 - 3 las velocidades de transmisión son mucho más lentas que los requeridos por la CPU en el procesamiento de esos datos, así como tambien durante el manejo del sistema de archivos, lectura y grabación, que son más lentos que las velocidades de la CPU en su proceso. Las animaciones y los juegos por ejemplo, requieren de una mayor optimización de los recursos de CPU. Al mostrarse un archivo o una imágen dentro de un navegador y acceder en seguida a una dirección electronica o URL para una búsqueda, es un ejemplo de aplicación multihilo. Sin esta característica, sería necesario esperar a que el archivo se bajara de la red o se cargara la imagen para poder acceder a una direccion URL para un proceso de búsqueda. Con la programación multihilo, se aprovecha el tiempo del procesador realizando otra tarea mientras se carga una imágen o un archivo. Por obvias razones los vizualizadores o navegadores en la red son multihilo. La programación multihilo permite acceder a los recursos de tiempo libre de la CPU, mientras se realizan otras tareas. Es otra de las razones de importancia para el estudio de sistemas basados en multihilos. El modelo multihilo está inmerso en el interprete de Java, ya que las bibliotecas de clases estan diseñadas teniendo en mente este modelo. Los hilos forman parte de todos los programas en Java. Un hilo dentro de un programa Java es un procedimiento que se puede ejecutar independiente del resto de una aplicación. La siguiente figura ilustra esquemáticamente un programa de un hilo y un programa de dos hilos. un hilo Programa con un solo hilo Figura 1 dos hilos Programa con dos hilos Programación de Hilos: Introducción 12 - 4 Cuando se ejecuta una aplicación en Java, se ejecuta el método main() en un hilo dedicado, así como también los applets se ejecutan en hilos dedicados, lo cual hace explícito que el concepto de hilo es esencial del trabajo en Java. Un hilo puede detenerse sin afectar las otras partes del programa. Por ejemplo en un hilo de animación, se hace que los bucles de la animación se detengan durante un tiempo, un segundo, sin hacer que se pare el resto del programa. Cuando un hilo se detiene o se bloquea, sólo el se detiene y los demás continúan con su ejecución. La creación de un hilo, como la necesidad de un procedimiento que se hará de manera concurrente con otros procedimientos, pasa por la definición del procedimiento que se llamará repetidamente, por ejemplo el procedimiento en un applet que muestra una animación, mientras se reproduce un clip de audio y se capturan los clicks del ratón. Aquí la animación consiste en exhibir a intervalos de tempo una secuencia de imagenes de tal manera que se de la sensación de movimiento. Ciclo de vida de un hilo Un hilo tiene un ciclo de vida que va desde su creación hsta su terminación. Durante su ciclo de vida cada uno de los hilos o tareas de una aplicación puede estar en diferentes estados, algunos de los cuales se indican a continuación: • Nacido: Cuando se acaba de crear un hilo, se dice que está nacido, y continúa en ese estado hasta que se invoca el método start() del hilo. La siguiente sentencia crea un nuevo thread pero no lo arranca, por lo tanto deja el thread en el estado de nacido. Thread miHilo = new MiClaseThread(); Cuando un thread está en este estado, es sólo un objeto Thread vacío o nulo. No se han asignado recursos del sistema todavía para el thread. Así, cuando un thread está en este estado, lo único que se puede hacer es arrancarlo con start(). • Listo: Cuando se invoca el método start() del hilo, se dice que está en estado listo. El método se arranca con la siguiente instrucción, para el caso del hilo miHilo: miHilo.start(); • Ejecutable: cuando el método start() se ejecuta, crea los recursos del sistema necesarios para ejecutar el thread, programa el thread para Programación de Hilos: Ciclo de vida de un hilo 12 - 5 ejecutarse, y llama al método run() del thread que se ejecuta en forma secuencial. En este punto el thread está en el estado ejecutable. Se denomina así puesto que todavía no ha empezado a ejecutarse. • En ejecución: Un hilo en estado de listo de la más alta prioridad, pasa al estado de ejecución, cuando se le asignan los recursos de un procesador, o sea cuando inicia su ejecución. Aquí el thread está en ejecución.Cada hilo tiene su prioridad, hilos con alta prioridad se ejecutan preferencialmente sobre los hilos de baja prioridad. • No ejecutable :Un hilo continúa la ejecución de su método run(), hasta que pasa al estado de no ejecutable originado cuando ocurre alguno de los siguientes cuatro eventos: Se invoca a su método sleep(). Se invoca a su su método suspend(). El thread utiliza su método wait() para esperar una condición variable. El thread está bloqueado durante una solicitud de entrada/salida. Por ejemplo, en el siguiente fragmento de codigo se pone a dormir miHilo durante 10 segundos (10.000 milisegundos): Thread miHilo = new MiClaseThread(); miHilo.start(); try { miHilo.sleep(10000); } catch (InterruptedException e){ } Durante los 10 segundos que miHilo está dormido, incluso si el proceso se vuelve disponible, miHilo no se ejecuta. Después de 10 segundos, miHilo se convierte en "Ejecutable" de nuevo y, si el procesador está disponible se ejecuta. Para cada entrada en el estado "No Ejecutable", existe una ruta de escape distinta y específica que devuelve el thread al estado "Ejecutable". Por ejemplo, si un thread ha sido puesto a dormir dutante un cierto número de milisegundos deben pasar esos milisegundos antes de volverse "Ejecutable" de nuevo. La siguiente es una secuencia de las acciones a realizar para cada entrada en el estado "No Ejecutable": Programación de Hilos: Ciclo de vida de un hilo 12 - 6 Si se ha puesto a dormir un hilo, deben pasar el número de milisegundos especificados en sleep(). Si se ha suspendido un hilo, se debe llamar a su método resume(). Si un hilo está esperando una condición variable, siempre que el objeto propietario de la variable renuncie mediante notify() o notifyAll (). Si un hilo está bloqueado durante la I/O, cuando se complete la I/O. • Muerto: Un hilo pasa al estado de muerto cuando se termina su método run(), o cuando se ha invocado su método stop(). En algún momento el sistema dispondrá entonces del hilo muerto. Un hilo puede morir de dos formas: Muerte natural: se produce cuando su método run() sale normalmente. Por ejemplo, el bucle while en este método es un bucle que itera 100 veces y luego sale. Por tanto el hilo morirá naturalmente cuando se llegue al final de la iteración, es decir se termina su método run(). public void run() { int i = 0; while (i < 100) { i++; System.out.println("i = " + i); } } Por muerte provocada: en cualquier momento llamando a su método stop(). El siguiente código crea y arranca miHilo luego lo pone a dormir durante 10 segundos. Cuando el thread actual se despierta, se lo mata con miHilo.stop(). El método stop() lanza un objeto ThreadDeath hacia al hilo a eliminar. El thread moririá cuando reciba realmente la excepción ThreadDeath. Thread miHilo = new MiClaseThread(); miHilo.start(); try { Thread.currentThread().sleep(10000); } catch (InterruptedException e){ }miHilo.stop(); El método stop() provoca una terminación súbita del método run() del hilo. Si el método run() estuviera realizando cálculos sensibles, stop() podría dejar el programa en un estado inconsistente. Normalmente, no se debería Programación de Hilos: Ciclo de vida de un hilo 12 - 7 llamar al método stop() pero si se debería proporcionar una terminación educada como la selección de una bandera que indique que el método run() debería salir. El método stop() se encuentra depreciado en la version JDK1.2.1. • Bloqueado: un hilo se encuentra en el estado bloqueado cuando el hilo realiza una solicitud de entrada/salida. Cuando termina la entrada/salida que estaba esperando, un hilo bloqueado queda en el estado listo. La clase Thread Como ya se ha establecido antes, un thread es un hilo de ejecución de un programa. La Maquina Virtual de Java (JVM) permite la ejecución de concurrente de múltiples hilos. En la clase Thread se encapsula todo el control necesario sobre los hilos de ejecución o tareas. Un objeto Thread se lo puede entender como el panel de control sobre una tarea o hilo de ejecución. Dispone de métodos para controlar el comportamiento de las tareas, los cuales se tratarán más adelante. Los siguientes son los principales métodos de Thread: Metodos de la clase Thread static int activeCount() Retorna el múmero de el hilo activo en el grupo de hilos. void checkAccess() Chequea si eel hilo que se esta ejecutando tiene permiso para modificar este hilo. static Thr ead currentThread() Retorna el objeto Thread que representa a la tarea que se está ejecutando. void destroy() Destruye la tarea actual. String getName() Retorna el nombre actual del hilo, asignado con setName(). int getPriority() Retorna un valor entre 1 y 10, correspondiente a la prioridad de la tarea en ejecución. Programación de Hilos: La clase Thread 12 - 8 Metodos de la clase Thread ThreadGro up void static boo lean getThreadGroup() Retorna el grupo de hilo a que pertenece el hilo actual. interrupt() Interrumpe el hilo. interrupted() Chequea si el hilo actual se ha interrumpido. boolean isAlive() Chequea si el hilo esta vivo. boolean isDaemon() Chequea si el hilo es un hilo de servicio a otros. boolean isInterrupted() Chequea si el hilo ha sido interrumpido. void join() Espera a que el hilo se muera. void join(long millis) Espera hasta millis milisegundos para que el hilo muera. void run() Constituye el cuerpo de una tarea o hilo. Es llamado por el método start(), despues que la tarea apropiada del sistema se haya inicializado. La tarea actual se detendrá cada vez que run() devuelva el control. Este es el único método de la interfaz Runnable. void setDaemon(boolean on) Marca este hilo como un hilo de servi iusuario o hilo demonio. void setName(String name) Permite asignarle a la tarea un nombre con el cual se la puede identificar.. void setPriority(int newPriority) Cambia la prioridad de la tarea, entre los valores 1 y 10. Programación de Hilos: La clase Thread 12 - 9 Metodos de la clase Thread static voi d sleep(long millis) Provoca que el intérprete de Java ponga la tarea a dormir durante un tiempo indicado en milisegundos, transcurrido dicho tiempo la tarea estará disponible para ejecución. void start() Le indica al interprete de Java que cree un contexto de ejecución de una tarea e inicie su ejecución. Acto seguido se invoca el método run() de esta tarea en el nuevo contexto de ejecución. Debe tenerse cuidado de llamar a start() no más de una vez para una tarea determinada. String toString() Retorna la cadena que representa el hilo, que incluye nombre, prioridad y grupo del hilo. static voi d yield() Hace que el interprete detenga temporalmente la ejecución del objeto del hilo y permita que el siguiente hilo disponible se ejecute. Prioridades El intérprete de Java utiliza prioridades, como valores enteros, para determinar el trato que le debe dar a cada hilo respecto de los demás. La prioridad de un hilo se utiliza para determinar cúando se pasa a ejecutar otro hilo, lo cual se denomina cambio de contexto de ejecución. Hilos con alta prioridad se ejecutan preferencialmente sobre hilos de menor prioridad. La clase Thread tiene tres constantes predefinidas para asignar la prioridad: MIN_PRIORITY (prioridad 1), NORM_PRIORITY (prioridad 5) y MAX_PRIORITY (prioridad 10). Los procesos a nivel de usuario se recomienda que tengan una prioridad alrededor de 5, tareas en segundo plano así como de redibujo de la pantalla o entrada/salida por la red, con una prioridad cercana a la minima. Las reglas que determinan cuando se hace una conmutación o cambio de contexto son: • Un hilo le cede voluntariamente el control a otro, por ejemplo, cuando Programación de Hilos: Prioridades 12 - 10 abandona explícitamente, al quedarse dormido, o bloqueado en espera de una operación de entrada/salida pendiente. Se seleccionara para la CPU aquel hilo que estando preparado para ejecución, tenga la prioridad más alta. • Un hilo es desalojado por otro de prioridad más alta, independientemente de lo que el hilo desalojado estuviera haciendo. • Si dos o más hilos de igual prioridad compiten por el recurso, se pueden repartir el tiempo de la CPU mediante algoritmos especiales (algoritmo circular o round-robin). Sin embargo se pueden presentar problemas, ante la igualdad de prioridad, debido a que los sistemas operacionales tratan de manera diferente la conmutación de contexto. El hilo principal Cuando la Maquina Virtual de Java arranca la ejecución de un programa, ya hay un hilo ejecutándose, denominado hilo principal del programa, que se ejecuta cuando comienza el programa. Su importancia radica en que: • Es el hilo desde el cual se crearán el resto de hilos del programa. • Debe ser el último hilo que termine su ejecución, ya que cuando este hilo finaliza, el programa termina. El hilo principal se puede controlar a través de un objeto Thread, mediante el llamado al método currentThread(), el cual devuelve una referencia al hilo donde es llamado. Ya con la referencia al hilo principal se lo puede controlar, como a cualquier otro hilo. Creación de hilos El método run() le permite a un hilo realizar su tarea, ya que su código implementa el comportamiento de ejecución de la clase Thread, pudiendo realizar cualquier operación que sea codificable en instrucciones Java. La clase Thread implementa un hilo genérico que por defecto no realiza nada, es decir que, la implementación de ese método dentro de la clase es vacía. La clase Thread (que es de por sí un objeto Runnable) permite que un objeto Runnable provea un método run() más interesante para los hilos. Hay dos manera de crear hilos: Extensión de la clase Thread Mediante la obtención de subclases de la clase Thread y creando luego una Programación de Hilos: Creación de hilos 12 - 11 instancia de esta clase. Esta nueva clase debe sobreescribir el método run de la clase Thread que es el punto de entrada del nuevo hilo. Dentro de la clase que utilice la instancia de la clase Thread, se debe llamar al método start() para que inicie la ejecución del nuevo hilo. La siguiente es la forma general del cuerpo de una clase hilo mediante extensión de la clase Thread: public class NombreClase extends Thread{ // clase que extiende a Thread ..... public void run(){ // cuerpo para sobreescribir run() ..... } } Dentro de la clase que usa a NombreClase, la siguiente es la forma del código para crear el hilo e iniciar su ejecución mediante el llamado al método start(): NombreClase unHilo = new NombreClase(); . . unHilo.start(); Ejemplo 1: Un hilo que genere los primeros N numeros enteros, intervalos de 100 miullisegundos, por el método de extensión de Thread tendría la siguiente estructura: class GeneraEnteros extends Thread { int n; GeneraEnteros(int valorN) { n = valorN; } public void run() { // calcula los n primeros terminos de la serie . . . } } Con el siguiente código se crea el hilo y se inicia su ejecución mediante el llamado al método start() en la clase que usa a GeneraEnteros: GeneraEnteros serie = new GeneraEnteros(10); serie.start(); Veamos el ejemplo codificado completamente, para la generación de los Programación de Hilos: Creación de hilos 12 - 12 primeros numeros enteros: public class GeneraEnteros extends Thread { int n; public GeneraEnteros(int valorN) { n = valorN; } public void run() { for (int i = 0; i < n; i++) { System.out.println("Numero "+i); try { sleep(1000); } catch (InterruptedException e) { System.out.println("Interrupcion hilo ");} } System.out.println("Termina hilo " ); } } import javax.swing.JOptionPane; public class UsaGeneraEnteros{ public static void main (String[] args) { int numero = Integer.parseInt (JOptionPane.showInputDialog("Cuantos terminos?")); GeneraEnteros serie =new GeneraEnteros(numero); serie.start(); } } Ejecutese el programa principal y observe que para un valor de 10, se obtiene la siguiente salida en la ventana del sistema: Numero 0 Numero 1 Numero 2 Numero 3 Numero 4 Numero 5 Numero 6 Numero 7 Numero 8 Numero 9 Termina hilo Press any key to continue... Observese que en la clase principal no se ha colocado la instrucción del método System.exit(0), ya que esto terminaria la ejecución del hilo del metodo main(), antes de terminar el hilo serie. Ejemplo 2: Se a continuación una aplicación que maneja dos hilos. El hilo Programación de Hilos: Creación de hilos 12 - 13 principal corresponde a main() y el segundo hilo a Hilo 1. Se utiliza la clase GeneraEnteros, con un constructor que le ingresa el nombre del hilo y la duración del tiempo de detencion del hilo: class GeneraEnteros extends Thread { int n, tiempo; String nombre; public GeneraEnteros(int valorN, String nombreHilo, int retardo) { n = valorN; nombre = nombreHilo; tiempo=retardo; } public void run() { for (int i = 0; i < n; i++) { System.out.println("Numero "+i+ " en " + nombre); try { sleep(tiempo); } catch (InterruptedException e) { System.out.println("Interrupcion "+ nombre);} } System.out.println("Termina "+nombre ); } } import javax.swing.JOptionPane; public class UsaGeneraEnteros{ public static void main (String[] args) { int numero = Integer.parseInt (JOptionPane.showInputDialog("Cuantos terminos?")); GeneraEnteros serie =new GeneraEnteros(numero, "hilo 1", 1000); serie.start(); try { for (int i = 0; i <5; i++) { System.out.println("valor i = "+i+" en Hilo principal en main()"); Thread.sleep(3000); } }catch (InterruptedException e){ System.out.println("Interrupci¢n del hilo principal en main()"); } System.out.println("Termina Hilo principal en main()"); } } Cuyos resultados para un valor de 10 elementos de la serie es: valor i = 0 en Hilo principal en main() Numero 0 en hilo 1 Numero 1 en hilo 1 Programación de Hilos: Creación de hilos Numero 2 en hilo 1 valor i = 1 en Hilo principal en Numero 3 en hilo 1 Numero 4 en hilo 1 Numero 5 en hilo 1 valor i = 2 en Hilo principal en Numero 6 en hilo 1 Numero 7 en hilo 1 Numero 8 en hilo 1 valor i = 3 en Hilo principal en Numero 9 en hilo 1 Termina hilo 1 valor i = 4 en Hilo principal en Termina Hilo principal en main() Exit code: 0 No Errors 12 - 14 main() main() main() main() Ejemplo 3: Acontinuación la aplicación UsaGeneraEnteros que maneja tres hilos: el hilo principal que corresponde a main() y los hilos, Hilo 1 e Hilo 2, instancias de la clase GeneraEnteros, cuya version es la misma que en el ejemplo anterior: import javax.swing.JOptionPane; public class UsaGeneraEnteros{ public static void main (String[] args) { int numero = Integer.parseInt (JOptionPane.showInputDialog("Cuantos terminos?")); GeneraEnteros serie1 =new GeneraEnteros(numero, "hilo 1", 1000); serie1.start(); GeneraEnteros serie2 =new GeneraEnteros(numero, "hilo 2", 100); serie2.start(); try { for (int i = 0; i <5; i++) { System.out.println("valor i = "+i+" en Hilo principal en main()"); Thread.sleep(3000); } }catch (InterruptedException e){ System.out.println("Interrupci¢n del hilo principal en main()"); } System.out.println("Termina Hilo principal en main()"); } } Cuyos resultados, al ejecutar UsaGeneraEnteros, para 5 elementos de la serie son: Programación de Hilos: Creación de hilos valor i = 0 en Hilo principal en Numero 0 en hilo 1 Numero 0 en hilo 2 Numero 1 en hilo 2 Numero 2 en hilo 2 Numero 3 en hilo 2 Numero 4 en hilo 2 Termina hilo 2 Numero 1 en hilo 1 Numero 2 en hilo 1 valor i = 1 en Hilo principal en Numero 3 en hilo 1 Numero 4 en hilo 1 Termina hilo 1 valor i = 2 en Hilo principal en valor i = 3 en Hilo principal en valor i = 4 en Hilo principal en Termina Hilo principal en main() Exit code: 0 No Errors 12 - 15 main() main() main() main() main() Ejemplo 3: Acontinuación la aplicación UsaGeneraEnteros que maneja cuatro hilos: el hilo principal que corresponde a main() y los hilos, Hilo 1, Hilo 2, e Hilo 3, instancias de la clase GeneraEnteros, cuya version es la misma que en el ejemplo anterior: import javax.swing.JOptionPane; public class UsaGeneraEnteros{ public static void main (String[] args) { int numero = Integer.parseInt (JOptionPane.showInputDialog("Cuantos terminos?")); GeneraEnteros serie1 = new GeneraEnteros(numero, "hilo 1", 1000); serie1.start(); GeneraEnteros serie2 = new GeneraEnteros(numero, "hilo 2", 100); serie2.start(); GeneraEnteros serie3 = new GeneraEnteros(numero, "hilo 3", 400); serie3.start(); try { for (int i = 0; i <5; i++) { System.out.println("valor i = "+i+" en Hilo principal en main()"); Thread.sleep(3000); } }catch (InterruptedException e){ System.out.println("Interrupci¢n del hilo principal en main()"); } System.out.println("Termina Hilo principal en main()"); Programación de Hilos: Creación de hilos 12 - 16 } } Cuyos resultados, al ejecutar UsaGeneraEnteros, para 5 elementos de la serie son: valor i = 0 en Hilo principal en Numero 0 en hilo 1 Numero 0 en hilo 2 Numero 0 en hilo 3 Numero 1 en hilo 2 Numero 2 en hilo 2 Numero 3 en hilo 2 Numero 1 en hilo 3 Numero 4 en hilo 2 Termina hilo 2 Numero 2 en hilo 3 Numero 1 en hilo 1 Numero 3 en hilo 3 Numero 4 en hilo 3 Numero 2 en hilo 1 Termina hilo 3 valor i = 1 en Hilo principal en Numero 3 en hilo 1 Numero 4 en hilo 1 Termina hilo 1 valor i = 2 en Hilo principal en valor i = 3 en Hilo principal en valor i = 4 en Hilo principal en Termina Hilo principal en main() Exit code: 0 No Errors main() main() main() main() main() Implementación de la interfaz Runnable Por este método se debe implementar la interfaz Runnable para la cual se debe sobreescribir el método run(). Una instancia de la clase puede ser creada y pasarla como argumento cuando se cree Thread e iniciarla. La interfaz Runnable debe ser implementada por cualquier clase cuyas instancias sean ejecutadas por un hilo. Dicha clase debe implementar el método run(). La implementación de la interface Runnable es la forma más habitual de crear tareas, ya que proporciona al desarrollador una forma para agrupar el trabajo de infraestructura de la clase. La interfaz establece el trabajo a realizar y la clase o clases que la implementan, indican como realizar ese trabajo. Se puede construir un hilo sobre cualquier objeto que implemente la interfaz Runnable. Para implementar esta interfaz, una clase sólo tiene que implementar el método run(). Dentro de run() se define el código que constituye el nuevo hilo. Despues Programación de Hilos: Creación de hilos 12 - 17 de haberse creado una clase que implemente la interfaz Runnable se requiere crear un objeto del tipo Thread dentro de esa clase, usando cualquiera de los constructores de Thread. La siguiente es la forma general del cuerpo de un hilo mediante implementación de la interfaz Runnable dentro de un applet, ya que, obviamente un applet no puede extender ademas a Thread, puesto que ya extiende a Applet: public class MiClase extends Applet implements Runnable{ Thread unHilo; public void start(){ if (unHilo == null){ unHilo = new Thread(this, “nombre del hilo”); unHilo.start(); .... } } public void run(){ if(unHilo!= null){ // cuerpo del hilo .... } } } Para una clase diferente a applet se tiene la siguiente forma general: publi class MiClase implements Runnable { Thread unHilo; MiClase(){ unHilo=new Thread(); } public void run() { if(unHilo!= null){ // cuerpo del hilo .... } } } La siguiente forma general crea el hilo e inicia su ejecución en la clase que usa a MiClase: MiClase miHilo = new MiClase(); new Thread(miHilo).start(); Ejemplo 4: Para el caso de la generación de los numeros enteros, se utiliza el cuerpo de la clase GeneraEnteros, modifinadolo como GeneraEnterosRun, mediante la implementación de la interfaz Runnable, lo mismo que la modificación en la clase UsaGeneraEnterosRun, cuyas versiones para el manejo Programación de Hilos: Creación de hilos 12 - 18 del hilo principal del método main() y un hilo de la clase GeneraEnterosRun se ilustra a continuación: class GeneraEnterosRun implements Runnable{ int n, tiempo; String nombre; Thread unHilo; public GeneraEnterosRun(int valorN, String nombreHilo, int retardo) { n = valorN; nombre = nombreHilo; tiempo=retardo; unHilo = new Thread();} public void run() { for (int i = 0; i < n; i++) { System.out.println("Numero "+i+ " en " + nombre); try { unHilo.sleep(tiempo); } catch (InterruptedException e) { System.out.println("Interrupcion "+ nombre);} } System.out.println("Termina "+nombre ); } } import javax.swing.JOptionPane; public class UsaGeneraEnterosRun{ public static void main (String[] args) { int numero = Integer.parseInt (JOptionPane.showInputDialog("Cuantos terminos?")); GeneraEnterosRun serie1 =new GeneraEnterosRun(numero, "hilo 1", 1000); new Thread(serie1).start(); try { for (int i = 0; i <5; i++) { System.out.println("valor i = "+i+" en Hilo principal en main()"); Thread.sleep(3000); } }catch (InterruptedException e){ System.out.println("Interrupci¢n del hilo principal en main()"); } System.out.println("Termina Hilo principal en main()"); } } Los siguientes son los resultados obtenidos con un valor de 10 en el numero de terminos a generar: Programación de Hilos: Creación de hilos valor i = 0 en Hilo principal en Numero 0 en hilo 1 Numero 1 en hilo 1 Numero 2 en hilo 1 valor i = 1 en Hilo principal en Numero 3 en hilo 1 Numero 4 en hilo 1 Numero 5 en hilo 1 valor i = 2 en Hilo principal en Numero 6 en hilo 1 Numero 7 en hilo 1 Numero 8 en hilo 1 valor i = 3 en Hilo principal en Numero 9 en hilo 1 Termina hilo 1 valor i = 4 en Hilo principal en Termina Hilo principal en main() Exit code: 0 No Errors 12 - 19 main() main() main() main() main() Ejemplo 5: Veamos a continuación el mismo problema pero resuelto mediante el applet AppleGeneraEnteros: import java.applet.Applet; import java.awt.Graphics; import java.awt.Color; import javax.swing.*; public class AppletGeneraEnteros extends Applet implements Runnable { Thread unHilo; int i, numero,tiempo, x, y; String nombre; public void start(){ nombre =JOptionPane.showInputDialog("Nombre hilo?" ); numero=Integer.parseInt(JOptionPane.showInputDialog("Numero de terminos ?" )); tiempo= Integer.parseInt(JOptionPane.showInputDialog ("Retardo ?" )); if(unHilo== null){ unHilo = new Thread(this); unHilo.start(); x=10; y=10; } } public void update(Graphics g){ setBackground(Color.white); g.drawString("numero "+i+" de = "+nombre, x,y); } public void run() { Thread thisThread = Thread.currentThread(); while (unHilo == thisThread){ for(i=0; i < numero; i++){ y=y+20; repaint(); Programación de Hilos: Creación de hilos 12 - 20 try { Thread.sleep(tiempo); } catch (InterruptedException e) { System.out.println("Interrupcion del hilo ");} } System.out.println("Termina hilo " ); unHilo= null; } } public void paint(Graphics g){ g.drawString("Generación enteros ",x,y); } } Y su respectivo archivo HTML: <HTML> <APPLET CODE="AppletGeneraEnteros.class" WIDTH="400" HEIGHT="400"> </APPLET> </HTML> Cuyo resultado se obtiene, para 10 terminos, según la siguiente pantalla del applet: Programación de Hilos: Creación de hilos 12 - 21 Ejemplo 6: A continuación una aplicación que maneja tres hilos. El hilo principal corresponde a main() y los hilos, Hilo 1 e Hilo 2 creados como instancias de GeneraEnterosRun, que implementa Runnable: import javax.swing.JOptionPane; public class UsaGeneraEnterosRun{ public static void main (String[] args) { int numero = Integer.parseInt (JOptionPane.showInputDialog("Cuantos terminos?")); GeneraEnterosRun serie1 =new GeneraEnterosRun(numero, "hilo 1", 1000); new Thread(serie1).start(); GeneraEnterosRun serie2 =new GeneraEnterosRun(numero, "hilo 2", 200); new Thread(serie2).start(); try { for (int i = 0; i <5; i++) { System.out.println("valor i = "+i+" en Hilo principal en main()"); Thread.sleep(3000); } }catch (InterruptedException e){ System.out.println("Interrupci¢n del hilo principal en main()"); } System.out.println("Termina Hilo principal en main()"); } } Los resultados al ejecutarse son los siguientes: valor i = 0 en Hilo principal en main() Numero 0 en hilo 1 Numero 0 en hilo 2 Numero 1 en hilo 2 Numero 2 en hilo 2 Numero 3 en hilo 2 Numero 4 en hilo 2 Numero 5 en hilo 2 Numero 1 en hilo 1 Numero 6 en hilo 2 Numero 7 en hilo 2 Numero 8 en hilo 2 Numero 9 en hilo 2 Numero 2 en hilo 1 Termina hilo 2 valor i = 1 en Hilo principal en main() Numero 3 en hilo 1 Numero 4 en hilo 1 Numero 5 en hilo 1 valor i = 2 en Hilo principal en main() Numero 6 en hilo 1 Programación de Hilos: Creación de hilos 12 - 22 Numero 7 en hilo 1 Numero 8 en hilo 1 valor i = 3 en Hilo principal en main() Numero 9 en hilo 1 Termina hilo 1 valor i = 4 en Hilo principal en main() Termina Hilo principal en main() Exit code: 0 No Errors Ejemplo 7: A continuación se presenta un programa que permite dibujar la fecha y la hora en la ventana de un applet que implementa Runnable, por medio de la creación de un hilo: import import import import java.awt.Graphics; java.awt.Font; java.util.Date; java.applet.Applet; public class Reloj01 extends Applet implements Runnable { Font theFont = new Font("TimesRoman",Font.BOLD,15); Date theDate; Thread runner; public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } public void stop() { if (runner != null) { runner = null; } } public void run() { Thread thisThread = Thread.currentThread(); while (runner == thisThread) { repaint(); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } public void paint(Graphics screen) { theDate = new Date(); screen.setFont(theFont); Programación de Hilos: Creación de hilos 12 - 23 screen.drawRect(1,1, getSize().width-2, getSize(). height-2); screen.drawString("" + theDate.toString(), 20, 20); } } <applet code="Reloj01.class" width=250 height=40> </applet> La salida es la siguiente: Ejemplo 8: El siguiente ejemplo dibuja 100 circulos a intervalos de 500 milisegundos en la ventana del applet. import java.awt.Graphics; import java.applet.Applet; import java.awt.Font; import java.util.Date; import java.awt.Color; public class Circulos01 extends Applet implements Runnable { int x1, y1; Thread runner; public void init(){ x1 = (int)(Math.random()*(getSize().width-10)); y1 = (int)(Math.random()*(getSize().height-10)); } public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } public void update(Graphics g){ setBackground(Color.white); g.drawOval(x1,y1,20,20); x1 = (int)(Math.random()*(getSize().width-20)); y1 = (int)(Math.random()*(getSize().height-20)); } public void stop() { if (runner != null) { runner = null; Programación de Hilos: Creación de hilos 12 - 24 } } public void run() { Thread thisThread = Thread.currentThread(); while (runner == thisThread ) { for(int i=1; i <= 100;i++){ repaint(); try { Thread.sleep(500); } catch (InterruptedException e) { } } System.out.println("Termina hilo"); runner= null; } } public void paint(Graphics g) { } } Ejemplo 9: Aquí se ha adaptado un programa que permite generar circulos y ponerlos en movimiento. La generación de circulos se hace mediante un boton, el cual al ser pulsado lo genera aleatoriamente en cualquier parte de la ventana. Se deja para estudio del lector y para que ejercite modificaciones que pueda realizar. Programación de Hilos: Creación de hilos 12 - 25 El programa fué tomado de Core Web Programing de PrenticeHall, 1997, desarrollado por Mary Hall, quien autoriza su libre uso o adaptación. import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.util.Vector; /* Movimiento de circulos alrededor de la pantalla. */ public class Bounce extends Applet implements Runnable, ActionListener { private Vector circles; private int width, height; private Button startButton, stopButton; private Thread animationThread = null; public void init() { setBackground(Color.white); width = getSize().width; height = getSize().height; circles = new Vector(); startButton = new Button("Inicia Circulo"); startButton.addActionListener(this); add(startButton); stopButton = new Button("Detiene Circulos"); stopButton.addActionListener(this); add(stopButton); } /* Cuando el boton "start" button is presionado se inicia un hilo de animacion si no se ha iniciado. De cualquier manera agrega un circulo al Vector de circulos que estan en movimiento. Cuando el boton Stop se presiona suspende el hilo y limpia el Vector. */ public void actionPerformed(ActionEvent event) { // para manejar los eventos de los botones if (event.getSource() == startButton) { if (circles.size() == 0) { // Erase any circles from previous run. getGraphics().clearRect(0, 0, getSize().width, getSize().height); animationThread = new Thread(this); animationThread.start(); } int radius = 25; int x = radius + randomInt(width - 2 * radius); int y = radius + randomInt(height - 2 * radius); int deltaX = 1 + randomInt(10); int deltaY = 1 + randomInt(10); circles.addElement(new MovingCircle(x, y, radius, deltaX, deltaY)); } else if (event.getSource() == stopButton) { if (animationThread != null) { Programación de Hilos: Creación de hilos 12 - 26 animationThread.stop(); circles.removeAllElements(); } } repaint(); } /* En cada iteracion se llama a paint() y se hace una pausa. El método paint(9 mueve los circulos y los dibuja */ public void run() { while(true) { repaint(); pause(100); } } public void update(Graphics g) { paint(g); } /* Borra cada circulo en su posicion anterior, lo mueve y lo dibuja de nuevo en la nueva posicion */ public void paint(Graphics g) { MovingCircle circle; for(int i=0; i<circles.size(); i++) { circle = (MovingCircle)circles.elementAt(i); g.setColor(getBackground()); circle.draw(g); // vieja posicion circle.move(width, height); g.setColor(getForeground()); circle.draw(g); // nueva posicion } } private int randomInt(int max) { double x = Math.floor((double)(max + 1) * Math.random()); return((int)(Math.round(x))); } // Duerme por un tiempo private void pause(int milliseconds) { try { Thread.sleep((long)milliseconds); } catch(InterruptedException ie) {} } } import java.awt.*; /* Clase para almacenar x, y, radius, con un método de dibujo */ public class SimpleCircle { Programación de Hilos: Creación de hilos 12 - 27 private int x, y, radius; public SimpleCircle(int x, int y, int radius) { setX(x); setY(y); setRadius(radius); } /* Dado un objeto Graphics, dibuja un SimpleCircle centrado alrededor de su posicion actual */ public void draw(Graphics g) { g.fillOval(x - radius, y - radius, radius * 2, radius * 2); } public int getX() { return(x); } public void setX(int x) { this.x = x; } public int getY() { return(y); } public void setY(int y) { this.y = y; } public int getRadius() { return(radius); } public void setRadius(int radius) { this.radius = radius; } } /* Extension de SimpleCircle que puede moverse alrededor de valores deltaX y deltaY. El movimiento continua en una direccion hasta que el borde del circulo toque una pared de la ventana, en cuyo caso rebota en otra direccion. */ public class MovingCircle extends SimpleCircle { private int deltaX, deltaY; public MovingCircle(int x, int y, int radius, int deltaX, int deltaY) { super(x, y, radius); this.deltaX = deltaX; this.deltaY = deltaY; } public void move(int windowWidth, int windowHeight) { setX(getX() + getDeltaX()); setY(getY() + getDeltaY()); bounce(windowWidth, windowHeight); } private void bounce(int windowWidth, int windowHeight) { int x = getX(), y = getY(), radius = getRadius(), deltaX = getDeltaX(), deltaY = getDeltaY(); if ((x - radius < 0) && (deltaX < 0)) setDeltaX(-deltaX); else if ((x + radius > windowWidth) && (deltaX > 0)) setDeltaX(-deltaX); if ((y -radius < 0) && (deltaY < 0)) setDeltaY(-deltaY); else if((y + radius > windowHeight) && (deltaY > 0)) Programación de Hilos: Creación de hilos 12 - 28 setDeltaY(-deltaY); } public int getDeltaX() { return(deltaX); } public void setDeltaX(int deltaX) { this.deltaX = deltaX; } public int getDeltaY() { return(deltaY); } public void setDeltaY(int deltaY) { this.deltaY = deltaY; } } <HTML> <BODY BGCOLOR="WHITE"><H1>Circulos rebotando</H1> <TABLE BORDER=5> <TR><TH> <APPLET CODE="Bounce.class" WIDTH=600 HEIGHT=400> </APPLET> </TABLE></BODY> </HTML> El siguiente es un resultado de ejecutar el applet: