sábado, 14 de julio de 2012

5.2.2. Estado de un Hilo


Estado de un Hilo
Un hilo puede estar en uno de cuatro estados, cuando acabas de crearlo es un hilonuevo pero que no está en ejecución ni está compitiendo por recursos del CPU, el siguiente estado se denomina ejecutable, no en ejecución, ejecutable indica que el hilo está compitiendo por el CPU contra los otros hilos activos, solamente un hilo estará en ejecución pero puede haber muchos hilos en estado ejecutable que en cualquier momento pueden tomar posesión del CPU, el tercer estado posible esdetenido, ahí simplemente el hilo no está compitiendo por el CPU y esto lo podíamos ver en el ejemplo pasado cuando ejecutábamos el método sleep y por último el estado muerto, esto sucede cuando el hilo termina de ejecutar el método run.
En la figura puedes ver gráficamente los cuatro estados y la manera de irse a cada uno de ellos. El estado nuevo es cuando creas una instancia o un ejemplar de la clase Thread o de alguna clase que extienda a Thread. Para que el hilo que creaste pase a estadoejecutable necesitas llamar al método start y el hilo estará en estado ejecutable que es compitiendo por el CPU, hasta que sucedan algunas cosas, por un lado si mandas llamar al método sleep automáticamente se irá al estado de detenido, cuando sale de este estado de detenido es cuando hayan transcurrido la cantidad de milisegundos que especificaste en el método sleep en ese momento el hilo vuelve a ser ejecutable, es decir sigue compitiendo por el CPU con los otros hilos activos, además si llamas al método wait se irá a estado de detenido y saldrá de este si ejecutas notify o notifyAll (esto no lo cubriremos en esta lección), otra causa es que el hilo haga una operación de entrada-salida en ese momento se bloquea hasta que se complete la operación de entrada salida y por último es llamar al método suspend en ese momento el hilo se va a estado de detenido y se queda ahí hasta que se haga una llamada al método resume éstos dos métodos están puestos en cursivas porque están desaprobados, es decir, no es recomendado su uso, la especificación técnica del por qué está fuera de los alcances de este curso pero simplemente es necesario tener en cuenta que la utilización de estos dos métodos está desaprobada y no es conveniente hacerla.
El cuarto estado, cuando nuestro hilo se muere, llegamos a él ejecutando el método run y terminando su ejecución, la otra causa puede ser que ejecutemos el método stop pero este método también está desaprobado así es que no lo utilizaremos, entonces la manera de terminar con nuestros hilos o matarlos es, simplemente que termine la ejecución del método run en forma natural.
 En los dos ejemplos anteriores nuestro método run era simplemente un for que se ejecutaba 10 veces, al terminar la ejecución del run automáticamente el hilo se moría. Y por último, cuando se está en estado ejecutable y se ejecuta el métodoyield el hilo cede el CPU a algún hilo de igual prioridad y comienza a competir por el CPU con los otros hilos, entonces el hecho de llamar yield es simplemente dar la posibilidad a que otros hilos de igual prioridad puedan ejecutarse y que un hilo no adquiera el CPU por tiempo indefinido.
  •  new Tread (...)
    - Creado, pero todavía no comienza ejecución
  •  start ( )
    - crea los recursos necesarios para su ejecución, lo deja listo para que la máquina virtual lo ponga a correr llamando al método run (), compite por los recursos del sistema.
  •  Bloqueado por I/O, sleep () o wait ()
    - La máquina virtual puede seleccionar otro hilo para ejecución, para salir de este estado la acción debe de corresponder la acción que la bloqueó.
  •  Muerto
    - Terminó run (), una vez muerto un hilo no puede volver a ejecución
Resumiendo, tenemos cuatro estados posibles de un hilo, nuevo, recién creado pero no comienza su ejecución después de ejecutar start se crean todos los recursos necesarios para hacer la ejecución, ejecutable comienza a competir por el CPU con otros hilos, tercer estado, detenido llegamos a él cuando el hilo se bloquea con una operación de entrada salida se invoca al método sleep o el método wait en ese momento la máquina virtual selecciona otro hilo para ejecutarlo y por último el estado muerto que para nosotros simplemente será el término de la ejecución del método run, es importante saber que una vez que un hilo está muerto no se puede volver a ejecutar, es decir no puede mandar llamar start otra vez. ¿Qué es lo que tienes que hacer?, es crear otra instancia de tu clase para que se cree un nuevo hilo.

 

Ejemplo

Veamos un ejemplo un poco más entretenido, en este caso será un applet, el archivo fuente es Hilos3.java, también tienes un archivo llamado hilos3.html que puedes abrir desde un browser para ver la ejecución.
Antes de comenzar a hablar de los componentes de este applet y como maneja hilos, veamos como es la ejecución en pantalla, lo que verás es tres contadores que irán desplegando el valor de cada uno a velocidades diferentes, además en el renglón inferior ves tres botones con los cuales puedes parar el conteo de cada uno de los contadores que mencionamos, lo importante aquí es que serán tres contadores que se ejecutarán en forma simultánea y contarán a diferente velocidad, entonces podrás hacer una pausa y cargar en el browser la página hilos3.html, ver la ejecución, jugar un momento con el applet y después volver a la lección donde continuaremos explicando la programación de este applet. En la filmina lo primero que hacemos es el desplegado gráfico de nuestro applet, tenemos 3 botones cuya etiqueta es para el 1, para el 2 y para el 3 y tenemos tres campos de texto cuyo valor inicial es el valor 0. Además tenemos tres objetos de la clase Cuenta que es realmente un hilo que es el que irá desplegando a diferentes velocidades cada uno de los contadores. En el método init establecemos el layout a un GridLayout de dos renglones y tres columnas agregamos los 6 componentes gráficos y sin olvidar el detalle de hacer que los botones respondan a eventos registrando el escuchador que en este caso es el applet con cada uno de los botones que dispararán los eventos de acción.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;

public class Hilos3 extends Applet implements ActionListener{
       Button boton1 = new Button("Para el 1");
       Button boton2 = new Button("Para el 2");
       Button boton3 = new Button("Para el 3");
       TextField cuenta1 = new TextField("0");
       TextField cuenta2 = new TextField("0");
       TextField cuenta3 = new TextField("0");
       Cuenta t1, t2, t3;

              public void init(){
                     setLayout(new GridLayout(2,3));
                     add(cuenta1);
                     add(cuenta2);
                     add(cuenta3);
                     add(boton1);
                     add(boton2);
                     add(boton3);
                     boton1.addActionListener(this);                      boton2.addActionListener(this);                      boton3.addActionListener(this);
              }

Continuando con nuestro applet vemos que en el método start, este método start es del applet, no tiene absolutamente nada que ver con la llamada start de un hilo, en el método start creamos los tres hilos, t1, t2 y t3 que son de la clase Cuenta, que son como puedes ver el constructor de Cuenta, requiere dos parámetros, el primero es la cantidad de milisegundos entre cada conteo, t1 dejará pasar medio segundo t2 un segundo completo y t3, dos segundos.
 El segundo parámetro es el campo de texto donde se irá desplegando el conteo cada hilo tendrá su propio campo de texto, en la parte final de start tenemos que arrancar los hilos correspondientes, ya los creamos pero como sabemos eso no es suficiente, tenemos que arrancarlos.
En el método stop lo que hacemos es paramos el conteo, es decir matamos al hilo, normalmente esto lo hacemos con una variable de control booleana como verás en la siguiente filmina, nuestro método run será controlado por esta variable booleana en el momento que sea falsa, se terminará la ejecución del ciclo y por lo tanto se terminará la ejecución del run y el hilo morirá. Y por último simplemente identificamos en el método actionPerformed que botón será presionado y simplemente hacemos la variable contando del hilo correspondiente igual a falso para matar el hilo, entonces como puedes ver aquí lo importante es la creación de los tres hilos los dos parámetros que se manda a cada uno y cómo podemos parar la ejecución de los hilos.
Ahora veamos como está programada la clase Cuenta y si es realmente la clase que permite ejecutar esto en forma simultánea, cada uno de los tres contadores.
       public void start(){
      
    t1 = new Cuenta(500,cuenta1);
          t2 = new Cuenta(1000,cuenta2);
          t3 = new Cuenta(2000,cuenta3);
          t1.start(); t2.start();
               t3.start();

     }
     public void stop(){
    
      t1.contando = false;
          t2.contando = false;
          t3.contando = false;

     }
     public void actionPerformed(ActionEvent e){
          if (e.getSource().equals(boton1))
              
t1.contando = false;
          else if (e.getSource().equals(boton2))
              
t2.contando = false;
          else if (e.getSource().equals(boton3))

              t3.contando = false;

     }
}
Nuestra clase Cuenta obviamente extiende a Thread, tenemos algunas variables de la clase por un lado tenemos un deltaT que es de tipo long, que es el que determinará la velocidad del conteo por otra parte tenemos una variable entera que es el número que llevará el conteo en este caso se llama cuenta y esinicializada al valor de 0. Además tenemos una variable caja que será un campo de texto, que no va a existir dentro de esta clase pero esa es la variable que va a contener una referencia al campo de texto correspondiente en el applet y por último una variable booleana contando que me controlará hasta donde vive el hilo. Después puedes ver el constructor de la clase Cuenta que recibe dos parámetros que ya mencionamos y que los guardamos en las variables de la clase Cuenta deltaT y caja. Esto es importante, porque necesitamos una referencia al campo de texto correspondiente en el applet para desplegar el conteo desde un objeto de la clase Cuenta sino, no podríamos tener acceso a los campos de texto del applet, necesitamos una referencia y esa referencia es la que se manda como parámetro. Y por último el método run, que es el corazón de nuestro hilo y de todos los hilos simplemente es un ciclo while mientras contando sea verdadera se irá ejecutando, se incrementa la variable cuenta en uno y después se despliega su valor en el campo de texto correspondiente, acto seguido simplemente se manda llamar sleep por un deltaT para parar el conteo y ese es todo nuestro ciclo. En el momento en que se sale del ciclo en el campo de texto correspondiente se despliega el letrero:ya se murió, indicando que el hilo ya está muerto, este es todo nuestro applet y si ya viste la ejecución puedes ver que los tres contadores, cuentan a diferente velocidad además lo importante es que los tres contadores son exactamente de la misma clase, simplemente las creas con diferente parámetro, entonces en el momento de ejecutar este applet los que tu tienes es cuatro hilos corriendo, el hilo donde corre el applet que es donde se despliegan los contadores y el que controla los eventos de los botones y cada uno de los tres hilos que ejecuta el conteo y actualiza su correspondiente campo de texto. Espero que con esto quede claro el concepto de hilos y que veas que ventaja reporta. Este programa sería prácticamente imposible realizarlo si es que no tuviéramos el concepto de hilos.
class Cuenta extends Thread{
long deltaT;
int cuenta=0;
TextField caja;
boolean contando = true;

    public Cuenta(long deltaT, TextField caja){
        this.deltaT = deltaT;
        this.caja = caja;
    }

    public void run(){
        while(contando){
            cuenta ++;
            caja.setText(Intejer.toString(cuenta));
            try{
                  sleep(deltaT);
            }
        }
        caja.setText("Ya se murió);
    }
}

No hay comentarios:

Publicar un comentario en la entrada