HILOS DEMONIO Los hilos de ejecución demonio también se llaman servicios, porque se ejecutan, normalmente, con prioridad baja y proporcionan un servicio básico a un programa o programas cuando la actividad de la máquina es reducida. Los hilos demonio son útiles cuando un hilo debe ejecutarse en segundo plano durante largos períodos de tiempo.Un ejemplo de hilo demonio que está ejecutándose continuamente es el recolector de basura (garbage collector). Este hilo, proporcionado por la Máquina Virtual Java, comprueba las variables de los programas a las que no se accede nunca y libera estos recursos, devolviéndolos al sistema. Un hilo puede fijar su indicador de demonio pasando un valor true al método setDaemon(). Si se pasa false a este método, el hilo de ejecución será devuelto por el sistema como un hilo de usuario. No obstante, esto último debe realizarse antes de que se arranque el hilo de ejecución (start()). Si se quiere saber si un hilo es un hilo demonio, se utilizará el método isDaemon(). //Demonios.java import java.io.*; class Demonio extends Thread{ private static final int TAMANIO = 10; private Thread[] t = new Thread[TAMANIO]; public Demonio(){ setDaemon(true); start(); } public void run(){ for(int i = 0; i<TAMANIO; i++){ t[i]= new EnjendrarDemonio(i); } for(int i=0; i<TAMANIO; i++){ System.out.println("t[" + i + "].isDaemon() = " + t[i].isDaemon() ); } while(true) yield(); } } class EnjendrarDemonio extends Thread{ public EnjendrarDemonio(int i){ System.out.println("Enjendrando demonio " + i ); start(); } public void run(){ while (true) yield(); } } public class Demonios{ public static void main(String[] args) throws IOException{ Thread d = new Demonio(); System.out.println("d.isDaemon() = " + d.isDaemon() ); System.out.println("presione cualquier tecla para terminar"); System.in.read(); } } Cambio de Contexto Si existe una interrupción pendiente es necesario: Salvar el contexto (PC, registros del procesador, información de la pila) del programa en ejecución. Poner en el PC la dirección del programa de tratamiento de la interrupción, que suele constar de unas pocas tareas básicas. Una pregunta que puede plantearse es: ¿qué es lo que constituye el contexto que se debe salvar? La respuesta es que se debe incluir información que pueda ser necesaria para reanudar el programa interrumpido. Así pues, debe guardarse la parte del bloque de control del proceso denominada información de estado del procesador. Esto incluye al contador de programa, otros registros del procesador y la información de la pila. ¿Se tiene que hacer algo más? Ello dependerá de lo que ocurra a continuación. La rutina de tratamiento de la interrupción es normalmente un programa corto que lleva a cabo unas pocas tareas básicas relacionadas con una interrupción. Por ejemplo, se marca el indicador que señala la presencia de una interrupción, puede enviar un acuse de recibo a la entidad que produjo la interrupción (como un módulo de E/S) y puede hacer algunas tareas básicas relacionadas con los efectos del suceso que causó la interrupción. Por ejemplo, si la interrupción está relacionada con un suceso de E/S, el gestor de interrupciones comprobará condiciones de error. Si se ha producido un error, la rutina de tratamiento puede enviar una señal al proceso que solicitó originalmente la operación de E/S. ¿Hay que hacer algo más? Pues depende de si la interrupción va a venir seguida de un cambio de proceso o no. La ocurrencia de una interrupción no siempre causa el cambio de proceso. Es posible que después de que el gestor de interrupciones se haya ejecutado, el proceso que estaba ejecutándose reanude su ejecución. En tal caso, tan sólo hay que guardar la información de estado del procesador y restaurarla para que pueda reanudarse correctamente el proceso interrumpido (estas funciones son realizadas en hardware). Por tanto, el cambio de contexto es un concepto distinto al cambio de un proceso. Puede ocurrir un cambio de contexto sin cambiar el estado del proceso que está actualmente en estado de ejecución. En tal caso, salvar el contexto y restaurarlo posteriormente involucra un pequeño coste extra. Sin embargo, si el proceso que estaba ejecutándose tiene que pasar a otro estado (listo o bloqueado), el sistema operativo tiene que llevar a cabo cambios substanciales en su entorno( contexto ). Los pasos involucrados en un cambio completo de proceso son los siguientes: 1.Salvar el contexto del procesador, incluyendo el contador de programa y otros registros. 2.Actualizar el PCB que estaba en estado de ejecución. Esto implica cambiar el estado del proceso a alguno de los otros estados (listo, bloqueado, suspendido_listo). También se tienen que actualizar otros campos, como uno en el que se guarde la razón por la que se abandona el estado de ejecución y otros con información de contabilidad. 3.Mover el PCB a la cola apropiada (listos, bloqueados por el suceso i, suspendido_listo). 4.Seleccionar otro proceso para ejecución (como veremos en el tema de Planificación de Procesos). 5.Actualizar el PCB seleccionado. Cambiar, por ejemplo, su estado a ‘en ejecución’. 6.Actualizar las estructuras de datos de gestión de la memoria. Esto puede hacer falta dependiendo de cómo se gestione la traducción de direcciones (lo dejaremos para los temas sobre memoria).Restaurar el contexto del procesador a aquél que existía en el momento en el que el proceso seleccionado dejó por última vez el estado de en ejecución, cargando los valores previos del contador de programa y de otros registros. Así pues, el cambio de proceso, que implica un cambio de contexto, requiere un esfuerzo considerablemente superior al de un cambio de contexto. Que mata al hilo de forma brusca Un hilo de ejecución se puede morir de dos formas: por causas naturales o porque lo maten (con stop()). También se puede matar en cualquier momento un hilo, invocando a su método stop(). En el trozo de código siguiente: Thread MiThread = new MiClaseThread(); MiThread.start(); try { MiThread.sleep( 10000 ); } catch( InterruptedException e ) { ; } MiThread.stop(); se crea y arranca el hilo MiThread, se duerme durante 10 segundos y en el momento de despertarse, la llamada a su método stop(), lo mata. El método stop() envía un objeto ThreadDeath al hilo de ejecución que quiere detener. Así, cuando un hilo es parado de este modo, muere asíncronamente. El hilo morirá en el momento en que reciba ese objeto ThreadDeath. Los applets utilizarán el método stop() para matar a todos sus hilos cuando el navegador con soporte Java en el que se están ejecutando le indica al applet que se detengan, por ejemplo, cuando se minimiza la ventana del navegador o cuando se cambia de página. El método isAlive() El interfaz de programación de la clase Thread incluye el método isAlive(), que devuelve true si el hilo ha sido arrancado (con start()) y no ha sido detenido (con stop()). Por ello, si el método isAlive() devuelve false, sabemos que estamos ante un Nuevo Thread o ante un thread Muerto. Si devuelve true, se sabe que el hilo se encuentra en estado Ejecutable o Parado. No se puede diferenciar entre Nuevo Thread y Muerto, ni entre un hilo Ejecutable o Parado. Prioridades de hilo El intérprete de Java utiliza prioridades para determinar cómo debe comportarse cada hilo con respecto a los demás. Las prioridades de hilo son valores entre 1 y 10 que indican la prioridad relativa de un hilo con respecto a los demás. Método que devuelve verdadero si un hilo es hilo demonio Si se quiere saber si un hilo es un hilo demonio, se utilizará el método isDaemon(). Métodos para la sincronización Si se utiliza una clase que no fue diseñada para accesos multihilo y, por ello, dispone de métodos no sincronizados que manipulan el estado interno, puede envolver la llamada al método en un bloque sincronizado. El formato general de la sentencia sincronizada es el siguiente: synchronized(objeto) sentencia; En el ejemplo, objeto es cualquier referencia al objeto, y sentencia suele ser un bloque que incluye una llamada al método de objeto, que solo tendrá lugar una vez que el hilo haya entrado con éxito en el monitor de objeto. Java proporciona un mecanismo elegante de comunicación entre procesos, a través de los métodos wait, notify y notifyAll. Estos métodos se implementan como métodos de final en Object, de manera que todas las clases disponen de ellos. Cualquiera de los tres métodos sólo puede ser llamado desde dentro de un método synchronized. wait: le indica al hilo en curso que abandone el monitor y se vaya a dormir hasta que otro hilo entre en el mismo monitor y llame a notify. notify: despierta al primer hilo que realizó una llamada a wait sobre el mismo objeto. notifyAll_: despierta todos los hilos que realizaron una llamada a wait sobre el mismo objeto. El hilo con mayor prioridad de los despertados es el primero en ejecutarse.