Tema 2 Programación Multihilo
Short Description
Descripción: Tema 2 Programación Multihilo...
Description
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
TEMA 2 PROGRAMACIÓN MULTIHILO 1. Programación paralela o multihilo 1.1. Hilos de ejecución de un proceso 1.2. Ventajas, y desventajas, de los hilos sobre los procesos
2. Hilos en JAVA 2.1. 2.2. 2.3. 2.4. 2.5. 2.6.
Clase Thread Clases e interfaces relacionadas con los Hilos Estados de ejecución de un hilo Mecanismos de control de hilos Mecanismos de prioridad en los hilos Sincronización de hilos
3. Otras herramientas multihilo de JAVA
Página 1 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
1. Programación paralela o multihilo Como vimos en el tema 1, la programación paralela o multihilo es un tipo de programación concurrente basada en la ejecución de varias tareas o hilos de ejecución, simultáneamente. Está diseñada para sistemas multiprocesador de memoria compartida.
1.1. Hilos de ejecución de un proceso Un hilo es una característica que permite a una aplicación realizar varias tareas a la vez, porque puede ser ejecutada en paralelo con otra tarea. Un hilo (hebra, thread, subtarea, subproceso...) es una secuencia de código en ejecución dentro de un proceso. Dentro de cada proceso puede haber varios hilos ejecutándose. Por ejemplo, Word puede tener un hilo en background chequeando automáticamente la gramática de lo que estoy escribiendo, mientras otro hilo puede estar salvando automáticamente los cambios del documento en el que estoy trabajando. Los distintos hilos de un proceso comparten una serie de recursos como el espacio de memoria, los archivos abiertos, situación de autenticación, etc. El hecho de que los hilos de ejecución de un proceso compartan recursos, significa que cualquiera de ellos puede modificar dichos recursos. Un proceso sigue en ejecución mientras al menos uno de sus hilos siga activo. Cuando todos los hilos de un proceso acaban, el proceso finaliza y todos sus recursos son liberados.
1.2. Ventajas, y desventajas de los hilos sobre los procesos Si bien los hilos son generados a partir de la creación de un proceso, podemos decir que un proceso es un hilo de ejecución, conocido como Monohilo. Pero las ventajas de los hilos se dan cuando hablamos de Multihilos, que es cuando un proceso tiene múltiples hilos de ejecución los cuales realizan actividades distintas, que pueden o no ser cooperativas entre sí. Las ventajas de los hilos sobre los procesos tienen que ver con el rendimiento pues permiten ahorrar tiempo y espacio de memoria, así por ejemplo: 1. Se tarda menos tiempo en crear un hilo de una tarea existente, que en crear un nuevo proceso. 2. Se tarda menos tiempo en terminar un hilo que en terminar un proceso. 3. Se tarda menos tiempo en cambiar entre dos hilos de una misma tarea que en cambiar entre dos procesos. 4. Es más sencillo la comunicación (paso de mensajes por ejemplo) entre hilos de una misma tarea que entre diferentes procesos.
Página 2 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
5. ... Por otro lado, la utilización de hilos tiene sus desventajas: 1. No todos los lenguajes de programación soportan multihilo 2. Hay que controlar los problemas relacionados con la comunicación y sincronización de hilos, cuando éstos comparten recursos: Inanición: un hilo no puede utilizar los recursos porque otro no los libera. Problemas de interbloqueo entre hilos (Deadlock), 2 hilos se bloquean mutuamente esperándose el uno al otro sin avanzar ninguno de los 2. Acceso a los recursos compartidos por 2 hilos, llamados recursos críticos. Zonas de exclusión mutua, secciones críticas: trozo de código donde solo se ejecuta un hilo en un momento dado... Condiciones de carrera: el resultado de la ejecución de un proceso depende del orden en que se ejecuten los hilos. Errores de inconsistencia en la memoria compartida.
2. Hilos en JAVA Java dispone de varios mecanismos para la programación paralela. Por ejemplo la clase Thread perteneciente a la API java.lang; y un conjunto de herramientas para sincronizar hilos, clases... pertenecientes a la API java.util.concurrent. En este punto nos vamos a centrar en la clase Thread.
2.1. Clase Thread Java es un lenguaje de programación orientado a objetos, concurrente, de propósito general y multiplataforma creado por Sun Microsystems. Es un lenguaje de alto nivel que permite trabajar en modo monohilo y en multihilo. Java admite programación multihilo (concurrente o paralela o multiproceso) gracias a objetos Thread. Un objeto del tipo Thread es un hilo de ejecución en un programa y JAVA permite que una aplicación tenga múltiples hilos de ejecución o tareas ejecutándose simultáneamente. En Java existen 2 tipos de thread: daemon thread (hilos demonio) y no daemon thread (hilos no demonio). Los primeros son hilos del sistema y los segundos son hilos de usuario.
2.2. Clases e interfaces relacionadas con los Hilos El lenguaje de programación Java proporciona soporte para hilos a través de una simple interfaz y un conjunto de clases que forman parte del paquete Java.lang y son las siguientes:
Página 3 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
1.
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
Thread: La clase Thread es la clase que encapsula todo el control necesario para implementar hilos de ejecución. La clase Thread tiene sus métodos descritos en: http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html de los cuales destacamos los siguientes métodos de clase (métodos estáticos que deben llamarse de forma directa en la clase Thread): a. currentThread(): devuelve el objeto Thread que representa al hilo que se está ejecutando. b. yield(): este método hace que el sistema pase del hilo de ejecución actual al hilo siguiente disponible. Así los hilos de menor prioridad no sufren inanición. c. sleep(long): este método pone a dormir el nº de milisegundos indicado como parámetro. Entre los métodos de instancia destacamos: d. start(): inicializa el hilo, solo se puede llamar una vez e. run(): Este método contiene el cuerpo de ejecución del hilo f. setPriority(int): asigna al hilo la prioridad pasada como parámetro g. getPriority(): devuelve la prioridad del hilo en ejecución h. setName(String): permite asignar un nombre al hilo i. getName(): devuelve el nombre del hilo
2.
Runnable: Java no soporta herencia múltiple de forma directa, es decir, no se puede derivar una clase de varias clases padre. Esto nos plantea la duda sobre cómo podemos añadir la funcionalidad de Hilo a una clase que deriva de otra clase, siendo ésta distinta de Thread. Para lograr esto se utiliza la interfaz Runnable que permite añadir la funcionalidad de un hilo a una clase simplemente implementando la interfaz, en lugar de derivándola de la clase Thread. Esta herramienta es muy útil y a menudo es la única salida que tenemos para incorporar multihilo dentro de las clases. Su descripción se encuentra en la web: http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html
3.
Object: La clase objeto está en la parte superior de la jerarquía de Java, es el padre de todas las clases. esto significa que cada clase Java hereda la funcionalidad proporcionada por la clase objeto. Aunque, no es una clase de apoyo a los hilos, la clase objeto proporciona métodos cruciales dentro de la arquitectura multihilo de Java. Estos métodos son wait, notify y notifyAll. Su descripción se encuentra en la web: http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
2.3. Estados de ejecución de un hilo El comportamiento de un hilo depende del estado en que se encuentre. Este estado define su modo de operación actual, por ejemplo, si se está ejecutando o no. Podemos conocer el estado de un hilo con el método de getState().
Página 4 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
Los estados en los que puede estar un hilo en Java son: • nuevo (New): Los hilos en estado new ya han sido creados y están listos para empezar a trabajar, pero aún no han sido llamados para que empiecen a realizar su trabajo. Un hilo está en el estado new a través del método homónimo. Por Ejemplo: Thread t1 = new Thread(...) •
Ejecutable (Runnable): Cuando se llama al método start() inicializamos el hilo y pasa al estado runnable (ejecutable).
•
En ejecución (running) Solo están ejecutándose los hilos inicializados que estén utilizando la CPU. El sistema operativo selecciona de entre los hilos en estado runnable, los que pasan al estado running y empieza a ejecutar el método run(). Bloqueado (Not running): El estado not running se aplica a todos los hilos que están parados por alguna razón. Cuando un hilo está en este estado, no se le asigna tiempo de CPU pero está listo para ser usado y es capaz de volver al estado runnable en un momento dado. Los hilos pueden pasar al estado not running a través de varias vías, por ejemplo: Si está dormido, porque se ha llamado al método sleep() Si está esperando, a través del método wait() Cuando el subproceso está bloqueado en una operación Entrada/Salida.
•
•
Dead: Un hilo entra en estado dead cuando finaliza su ejecución.
2.4. Mecanismos de control de hilos 2.4.1. Creación de Hilos Un hilo en Java se crea como una instancia de la clase java.lang.Thread. Para definir e instanciar un nuevo hilo, hay dos formas: 1. Extendiendo la clase Thread, esto es, creando una subclase de esta clase, 2. Implementando la interfaz Runnable. Creación de hilos extendiendo la clase Thread En Java una hebra o hilo, es una clase por tanto la manera más sencilla de crear una hebra es crear una clase que herede de la clase Thread, la cual podríamos instanciar después. Por ejemplo: class MiHilo extends Thread { public void run() { ... } }
// MiHilo Hereda la clase Thread // redefine el método run()
En este caso se pueden heredar los métodos y variables de la clase padre. Pero, una misma subclase solamente puede extender o derivar una vez de la clase padre Thread.
Página 5 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
Creación de hilos Implementando la interfaz Runnable Java no admite herencia múltiple, es decir una clase solo puede heredar o extender una clase pero, puede implementar muchas interfaces. Runnable es un mecanismo que permite esta posibilidad. Si creamos hilos implementando la interfaz Runnable, se puede heredar una clase de cualquier otra e implementar otras interfaces pero, sin perder el comportamiento de la nuestra. Para crear un hilo con esta interfaz haríamos así: public class MiThread implements Runnable { Thread t; public void run() { // Ejecución del thread una vez creado ...} } Creación de objetos de la clase Thread La creación de objetos de la clase Thread se realiza a través del método new(), que tiene varios constructores, entre ellos: • Thread(); • Thread(objeto runnable); • Thread(objeto runnable, String nombre); • Thread(String nombre); 1. Para instanciar o crear un hilo, extendiendo de Thread, haríamos así: ... MiHilo h = new MiHilo(); ... 2. Para instanciar o crear un hilo con la interfaz runnable haríamos así: ... MiThread h = new MiThread(); Thread t =new Treadh(h); O lo que es lo mismo: Thread t =new Treadh(new(MiThread(h)); 2.4.2. Inicialización de un hilo En el punto anterior hemos visto como crear nuevos hilos. Cuando creamos hilos con new(), éstos están en estado new. Para poder ejecutarlos hay que inicializarlos y así pasarlos al estado runnable. La creación e inicio de los hilos se hace dentro del método main().
Página 6 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
El inicio de los hilos se realiza con el método start(). Este método crea los recursos necesarios para que el hilo pueda ejecutarse, lo incorpora a la lista de procesos disponibles para ejecución y llama al método run(). Un hilo solo puede iniciarse una vez, por tanto este método no se puede llamar más de una vez. 2.4.3. Ejecución de un hilo Una vez creado e inicializado un hilo, éste pasa del estado runnable al estado runnig cuando el scheduler lo selecciona y ejecuta el método run(). En el método run se implementa el código correspondiente a la acción o tarea que el hilo debe desarrollar. Los hilos trabajan con el método run() sin argumentos. Cuando ejecutamos un programa con varios hilos de ejecución podremos comprobar que el comportamiento de los hilos no es predecible. Los hilos no necesariamente se ejecutan en el mismo orden en que fueron introducidos. El scheduler controla el tiempo de CPU de cada hilo y lo único seguro es que cada hilo se ejecutará hasta terminar su tarea. Si tenemos varios hilos en ejecución y queremos saber cual se está ejecutando en un momento dado, el método Thread.currentThread.getName() devuelve el nombre del hilo en ejecución. 2.4.4. Suspensión o bloqueo de hilos El S.O. asigna tiempos de CPU a los hilos inicializados con el método start(). Pero a veces es necesario detener temporalmente uno de ellos, cambiando su estado a bloqueado, o not running. Para detener un hilo tenemos varias opciones: i.
A través del método sleep(long), lleva al hilo a un estado de “dormido”, durante un número de milisegundos concreto. El hilo vuelve al estado ejecutable cuando pase el nº de ms indicado. Si se necesita activar el proceso antes de que se agote el periodo de inactividad, se puede utilizar el método interrupt(). El método sleep() lanza la excepción InterruptedException, por ello hay que incluirlo dentro de un bloque try..catch .
ii.
A través del método wait() o wait(nº ms) el hilo de ejecución espere en estado dormido hasta que se le notifique que continúe. En wait() utilizaremos notify() o notifyAll() para despertar el hilo. El método notify informa a un hilo en espera de que continúe con su ejecución. El método notifyAll es similar a notify excepto que se aplica a todos los hilos en espera. En wait (nº ms) el hilo queda esperando hasta que sea despertado a través de notify o notifyAll o hasta el nº de ms indicado. Estos métodos se suelen utilizar para codificar bloques de sincronización o secciones críticas, donde es necsario sincronizar hilos que necesitan acceder a un mismo recurso: dato, archivo, base de datos... por ejemplo: el problema del productor-consumidor.
Página 7 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
iii.
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
Cuando el hilo está esperando operaciones de E/S. Al finalizar la operación de E/S el hilo recupera el estado ejecutable.
Este ejemplo muestra una aplicación JAVA de creación de hilos extendiendo la clase Thread: Ejemplo 1: archivo Hilo1.java public class Hilo1 extends Thread { private String nombre; public Hilo1(String nombre) { this.nombre=nombre; } /** El método run genera un nº aleatorio de ms, que será el tiempo que el hilo estará dormido antes de visualizar su nombre y el retardo o tiempo que ha permanecido dormido**/ public void run() { try{ int x=(int)(Math.random()*5000); Thread.sleep(x); System.out.println("Soy "+nombre+ " ("+x+")"); } catch (Exception ex){ } } public static void main(String[] args) { Hilo1 t1 = new Hilo1("Pedro"); Hilo1 t2 = new Hilo1("Pablo"); Hilo1 t3 = new Hilo1("Juan"); t1.start(); //método que inicializa el hilo y llama a run() t2.start(); t3.start(); } } El siguiente ejemplo muestra una aplicación JAVA creando hilos con Runnable: Ejemplo 2: archivo hilo2.java
Página 8 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
public class Hilo2 implements Runnable{ private String nombre; public Hilo2(String nombre) { this.nombre=nombre; } public void run(){ try{ int x=(int)(Math.random()*5000); Thread.sleep(x); System.out.println("Soy "+nombre+ " ("+x+")"); } catch (Exception ex){ } } public static void main(String[] args) { Thread t1 = new Thread(new Hilo2("Pedro")); Thread t2 = new Thread(new Hilo2("Pablo")); Thread t3 = new Thread(new Hilo2("Juan")); t1.start(); t2.start(); t3.start(); } } En este caso necesitamos crear una instancia de Thread antes de que el sistema pueda ejecutar el proceso como un hilo. La diferencia entre ambos métodos de creación de hilos en Java radica en la flexibilidad con que cuenta el programador, que es mayor en el caso de la utilización de la interfaz Runnable. La mayoría de las clases creadas que necesiten ejecutarse como un hilo implementarán la interfaz Runnable, ya que así queda cubierta la posibilidad de que sean extendidas por otras clases. Por ejemplo, si quisiera crear un applet con hilos de ejecución: public class miapplet extend Applet implements Runnable Una vez declarada la clase que implementa la interface Runnable, ya puede ser instanciada e iniciada como cualquier thread y cualquier subclase descendiente de esta clase poseerá también las características propias de los threads. 2.4.5.
Finalización de un hilo
En condiciones normales un hilo finaliza cuando terminan de ejecutarse todas las instrucciones de su método run(). También se puede finalizar con el método stop, Página 9 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
siempre que no sea el de la clase Thread. Dicho método se ha quedado obsoleto porque genera estados de incoherencia en algunos objetos del programa, provocados por un bloqueo o desbloqueo incorrecto de las instancias de los objetos. En su lugar podemos crear un método propio en un hilo del siguiente modo: public void stopMe(){ stopMe()=null; } Un método que nos permite comprobar si un hilo está vivo o no es la función miembro isAlive(). Este método devuelve un valor booleano. Por ejemplo: public static void main(String args[]) { MiThread t = new MiThread(); System.out.println("isAlive() antes de iniciar: "+t.isAlive()); t.start(); System.out.println("isAlive() en ejecución: "+t.isAlive()); } La salida correspondiente al programa es: isAlive() antes de iniciar el hilo: false isAlive() en ejecución: true Devolverá true en caso de que el hilo t1 haya iniciado su ejecución. En otro caso, devolverá false. ¿Qué resultado devolvería esta función, si el hilo hubiese sido bloqueado o dormido? 2.4.6.
Esperar a que finalice un hilo
A veces necesitamos esperar a que finalice un hilo o grupo de ellos para seguir adelante con otras tareas, pero como los hilos se ejecutan concurrentemente con el programa que los lanzó, esto puede provocar salidas de programa no deseadas relacionadas con el orden de ejecución de las acciones programadas. Si queremos que una acción se realice después de finalizar un hilo hay que esperar a que éste acabe, utilizando el método join(). Este método puede incluir como parámetro el nº de ms que queremos esperar a que acabe el hilo. En el siguiente ejemplo, para que el mensaje de cada hilo se visualice en el mismo orden que se introdujo, será necesario llamar al método join(). Ejemplo3:
Página 10 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
archivo Hilojoin.java public class Hilojoin extends Thread { private String nombre; public Hilojoin(String nombre) { this.nombre=nombre; } public void run(){ try{ int x=(int)(Math.random()*5000); Thread.sleep(x); System.out.println("Soy "+nombre+ " ("+x+")"); } catch (Exception ex){ } } public static void main(String[] args) throws InterruptedException { Hilojoin t1 = new Hilojoin("Pedro"); Hilojoin t2 = new Hilojoin("Pablo"); Hilojoin t3 = new Hilojoin("Juan"); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println("fin del proceso"); } } ¿Qué pasaría si no utilizásemos el método join?
2.5. Mecanismos de prioridad en los hilos En los ordenadores que tienen una única CPU, los hilos se ejecutan proporcionando la ilusión de que lo hacen al mismo tiempo. Se puede modificar la prioridad de los hilos después de su creación mediante setPriority(). A esta función se le pasa un entero comprendido entre dos valores mínimo y máximo. Cuanto mayor sea el entero, mayor será la prioridad con la que se ejecuta el hilo.
Página 11 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
Cuando se crea un hilo en Java, éste hereda la prioridad de su padre, el hilo que lo ha creado. A partir de aquí se le puede modificar su prioridad en cualquier momento utilizando el método setPriority. La clase Thread define tres constantes que representan los niveles de prioridad relativos para los subprocesos: • MIN_PRIORITY vale 1 • MAX_PRIORITY vale 10 • NORM_PRORITY Tomando 1 el valor de la mínima prioridad y 10 el valor de la máxima prioridad. Un ejemplo de hilo de baja prioridad es el que libera la memoria no usada que se ejecuta en la Máquina Virtual Java. Aún cuando la liberación de memoria es una tarea muy importante, su baja prioridad evita que el procesador esté ocupado demasiado tiempo en ella, dejando a los procesos críticos el uso prioritario de la CPU. Pero si en un momento dado la memoria se agotara, los subprocesos críticos entran en estado de espera, dejando a la CPU que ejecute el subproceso que libera la memoria no usada (el de menos prioridad). Para saber el nivel del prioridad de un subproceso se usa la función getPriority(): int prioridad=hilo1.getPriority(); Se puede cambiar la prioridad de un subproceso respecto de otro aumentando o disminuyendo su valor de prioridad, por ejemplo: hilo2.setPriority(hilo1.getPriority()+1); Se ejecutará primero el hilo de prioridad superior, y cuando éste finaliza o se convierte en “No Ejecutable”, comienza la ejecución de un hilo de prioridad inferior. El hilo seleccionado se ejecutará hasta que: 1. Un hilo con prioridad mayor pase a ser “Ejecutable”. 2. Abandone, o termine su método run. Los sistemas operativos no están obligados a tener en cuenta la prioridad de los hilos. Cuando todos los hilos tienen la misma prioridad, y queremos que la CPU cambie de hilo de forma equilibrada, se utiliza el método yield(). Este método indica a la JVM (máquina virtual de java) que el hilo en ejecución puede pasar del estado running a runnable, permitiendo así que otros hilos se ejecuten. Para probar la prioridad en hilos, vamos a partir del siguiente archivo: Ejemplo 4: archivo hilo.java public class hilo extends Thread { Página 12 de 23
IES FRANCISCO AYALA GRANADA MÓDULO: PSP
CURSO 2013-2014 TEMA 2
GRUPO: 2º DAM PROGRAMACIÓN MULTIHILO
public hilo(String nombre) { super(nombre); } @Override public void run(){ for(int i=1; i
View more...
Comments