Hilos en Java Hemos visto ya lo que es un Proceso. Una definición sencilla podría ser la de un programa en ejecución que tiene instrucciones, unos recursos asociados y un estado. Un Proceso puede competir por el procesador, puede ser bloqueado, etc. Un Hilo de ejecución, por otra parte, es la secuencia más pequeña de instrucciones que el Scheduler puede gestionar de forma independiente. En general, podemos decir que en un Proceso pueden existir uno o más Hilos. Esto es guay porque como ya sabes, los Procesos no comparten recursos entre sí, pero los Hilos sí pueden, y a demás es posible hacer que partes separadas de un Proceso se ejecuten a la vez (concurrentemente). ¿Te acuerdas de los programas en C sobre Procesos? En ellos las variables ‘se clonaban’, de tal manera que dos Procesos Padre e Hijo tenían cada uno su propia variable. Si querías comunicar dos procesos tenías que inventarte algo extravagante, como un Pipe. Pues bien, los Hilos permiten hacer cosas como compartir la memoria dinámica entre ellos, lo que significa que cada Hilo Padre e Hijo accederían a la misma variable común. Ciclo de Vida Los Hilos en Java tienen un Ciclo de Vida, cada uno con un diferente estado. • New: Cuando se crea un hilo nuevo. En código, se correspondería a hacer: • Start: Un hilo no hace nada hasta que no se le arranca. Entonces pasa a ejecutar el código que tiene asignado en un método llamado run (). • Stop: Este método ‘mata’ a un Hilo. Es más habitual dejar que el Hilo termine lo que está haciendo por su propia cuenta, pero a veces hay que hacerlo. 1 • Wait, Suspend y Sleep: Un Hilo puede ser parado cuando se le suspende, se le duerme, cuando está bloqueado en un proceso de entrada/salida, o cuando el hilo utiliza su método espera a que se cumpla una determinada condición. Son cuatro instrucciones diferentes, todas ellas detienen el Hilo, pero cada una tiene una manera de que vuelva a pasar al estado Ejecutable: o Si un hilo está dormido, pasado el lapso de tiempo. o Si un hilo está suspendido, después de una llamada a su método resume (). o Si un hilo está bloqueado en una entrada/salida, cuando concluya su ejecución. o Si un hilo está esperando una condición, cada vez que la variable que controla esa condición varíe debe llamarse al método notify () o notifyAll (). • IsAlive: Es un método que permite saber si un hilo está en Ejecución o Parado. • Yield: Cuando un Hilo no está haciendo nada importante, podemos hacer que le sugiera al Scheduler que le ignore en favor de otros Hilos. Para eso es yield (), pero no nos tienen por qué hacer caso. Trabajando con Hilos En Java para trabajar con Hilos lo que hacemos es escribir una Clase que herede de Thread (o implemente Runnable, da igual, ya lo veremos). Esta nueva Clase tendrá sí o sí un método run () en el que nosotros pondremos el código que queremos que ejecute el Hilo. Por tanto, desde cualquier punto del programa podemos hacer simplemente: Y como por arte de magia, se ejecutará a la vez el código del programa Y el del run (). Bueno vale, esto es más complicado que escribir una clase y cuatro líneas de código. Los Hilos plantean una serie de problemas, como puede ser la Sincronización. Esto sucede por ejemplo cuando queremos evitar que dos Hilos accedan a un mismo recurso; o cuando un Hilo tiene que esperar a que otro Hilo termine antes de continuar. A parte, la gestión de un Hilo al que se le duerme, se le despierta, etc. se hace siempre mediante bloques de try – catch. Lo habitual suele ser que dentro del try se para el Hilo, y desde otro Hilo se envíe un notify () para despertarlo. Esto genera una Excepción que hace que se ejecute el bloque catch del hilo despertado. Aunque andar pendientes todo el rato de los bloques try – catch es un poco jaleo, lo difícil de programar Hilos en Java en realidad es ‘imaginarse’ lo que quieres hacer y pasarlo a código fuente. En código en sí es algo bastante sencillo, no tiene mucha dificultad. 2 En este esquema tienes un ejemplo simple (y aproximado) de un programa que trabaja con Hilos en Java. En Verde puedes ver la ejecución principal del programa, y en Azul la del Hilo (Thread). Lo que sucede es que, en cuanto llegamos al paso 3, se crea la clase MiThread. Al decirle start () se ejecutará el método run () de MiThread, pero a la vez el programa principal seguirá ejecutándose por el paso 5. De esta manera podemos delegar a un Hilo las tareas repetitivas, por ejemplo: - Tenemos una clase de acceso a Base de Datos. Sabemos que la Base de Datos puede tener hasta 10 conexiones simultáneas, así que las abrimos las 10 de forma permanente y le encargamos a un Hilo diferente que gestione cada conexión. En principio, los Hilos están parados, pero si nos llega una Insert le mandamos a un Hilo ejecutar la sentencia. El programa principal gestiona los Hilos, de forma que en un momento dado puedes estar atendiendo hasta 10 operaciones SQL a la vez. - Tenemos un programa que está controlando por Internet las señales de alarma de una estación eólica (esto es un caso real). Cada vez que llega una señal hay que hacer algo con ella, pero también hay que reenviarla a otro equipo, registrar la alarma en una tabla de BBDD, y escribirla en un fichero de log. Un programa convencional Java tardaría 1 segundo en hacer todo esto de forma secuencial, algo inadmisible si te están llagando alarmas todo el rato. ¿Solución? Delegas la tarea pesada a varios Hilos para que todo suceda a la vez. Ahora cada alarma se procesa en 0,5 segundos. - Google tiene su página del buscador web metido en un servidor, que no deja de ser un programa normal y corriente. Cada vez que alguien quiere buscar algo en Google, teclea su dirección en un navegador, pone lo que quiere y le da al botón. ¿Cómo hace el servidor para atender simultáneamente las peticiones de millones y millones de usuarios? Pues efectivamente, haciendo que su buscador genere un Hilo que atienda a cada petición de cada usuario. … bueno, y porque son Google y les sobra la pasta, y si tienen que poner mil millones de servidores pues los ponen y ya. 3