Sólo Programadores Banner 468x60
Este mes en Sólo Programadores
Contenido del CD-ROM
Índice temático
Suscripción

JAVA

Utilización de threads (I)

La utilización de threads (literalmente hilos) permite la ejecución simultánea de varios flujos de control que pueden realizar diferentes tareas e incluso interactuar entre ellas. Mediante el uso de Java se pueden desarrollar programas con hilos de una forma simple y efectiva, permitiendo hacer uso de nuevas y potentes tecnologías.

Como usuarios estamos familiarizados con las aplicaciones multithread. Por ejemplo, los procesadores de texto imprimen los documentos sin que por ello uno se tenga que detener a que termine la impresión, se puede continuar escribiendo un nuevo texto o continuar el actual. Incluso, los navegadores de Internet visualizan varias imágenes a la vez, sin que ello nos pueda parecer nada extraño. Todo esto es posible gracias a la programación con threads.

¿QUÉ ES UN THREAD?

Un thread (hilo o contexto de ejecución) es un flujo de control de un programa, esto es, una secuencia de instrucciones en ejecución. El concepto que debemos acuñar como thread es similar al de proceso, excepto que múltiples thread pueden ejecutarse dentro del mismo proceso, compartiendo los datos y el código de programa. La mayoría de los programas se pueden considerar como de thread único, es decir, existe un único camino de ejecución, el programa tiene un inicio, se realizan una serie de cálculos y se finaliza. Los programas multihilo pueden tener varios flujos de control, varios threads que se desarrollan en diferentes partes del código ejecutándose "simultáneamente".

En un proceso típico con varios threads, ninguno, uno o varios hilos pueden están ejecutándose a la vez. Cuando se dice "a la vez", hay que tener en cuenta el número de procesadores que tengamos en el ordenador sobre el que se ejecuta el proceso. Una máquina con <n> procesadores puede ejecutar hasta <n> threads en paralelo. Si únicamente se dispone de un procesador, se podrán ejecutar todos los thread del proceso compartiendo el tiempo que se le ha adjudicado a cada proceso, de forma que aparentemente todos los thread se están ejecutando simultáneamente.

CARACTERÍSTICAS DE LOS HILOS

Como ya hemos comentado, el uso de hilos en los programas permite la ejecución simultánea de varias tareas dentro de un mismo programa, además una mejora en rendimiento del mismo. Todo esto es posible gracias a las características de los threads, que se describen a continuación:

  • Los threads de un proceso comparten el código que se ejecuta, luego sólo hay una porción de código ocupando memoria, siendo además una ocupación mínima. Además el trasiego de threads que tendrá que hacer el sistema operativo será escaso, entendiendo como tal el cambio que deberá realizar el sistema para colocar un proceso en ejecución y colocar el que se está ejecutando en espera. Este proceso se denomina cambio de contexto.
  • Cada thread tiene su propia pila y sus propios valores para sus variables, por lo que se define su estado de ejecución, así como el flujo de control.
  • Permiten un sencillo manejo de la memoria compartida, tanto en lo referente a datos como a variables, así como una forma práctica de comunicación entre ellos.

El uso de threads en un programa permite la ejecución "simultánea" de diferentes tareas.

Las principales ventajas del uso de hilos frente a los procesos son que el cambio de contexto entre dos hilos es mucho más "barato" que entre dos procesos, ya que por ejemplo, el código de los diferentes threads como se ha comentado anteriormente es compartido. Además los hilos únicamente se diferencian entre sí por los valores de la pila y variable de cada hilo, y el tratamiento de memoria compartida resulta más sencillo y práctico.

Pero también la programación con threads tiene sus inconvenientes, siendo el principal debido a que durante el desarrollo de aplicaciones, resulta complicado el proceso de depuración. Esto es debido a que existen varios flujos de control que no se reproducen en el mismo orden debido a la asignación de tiempos (no olvidemos que aunque depuramos un código de programa único estamos tratando con varios flujos, varias ejecuciones simultáneas), y las herramientas que actualmente están disponibles no ofrecen muchas facilidades. Por lo tanto, programación con threads requiere cierto manejo, experiencia y paciencia, que como no, se ve recompensada por los resultados finales.

UN EJEMPLO PRÁCTICO

Un clásico de la programación tradicional es el conocido HelloWorld, como programa que visualiza ese mismo mensaje. Ese programa a su vez puede ser ejecutado mediante un thread como se detalla a continuación:


public class mensaje extends Thread { 

            public void run() { 

                  System.out.println ( " Hello World "); 

            } 

public static void main ( String args[] ) {

            mensaje miHilo = new mensaje (); 

            miHilo.start(); 

            } 

} 

CREACIÓN DE THREAD
Para crear un nuevo thread es necesario generar una instancia de la clase java.lang.Thread. Un objeto de este tipo representa un verdadero hilo en ejecución para el intérprete de Java y es utilizado para el control y sincronización durante su aplicación. Mediante este objeto, podemos dormir el hilo, arrancarlo, suspenderlo, etc. Cabría esperar que el constructor de la clase java.lang.Thread sólo necesitase saber por dónde debería comenzar el hilo a ejecutarse, pero desafortunadamente no es así, por lo que para crear un thread se presentan dos alternativas posibles que vamos a exponer a continuación:
  • Declarar el hilo con una clase extendida de la clase java.lang.Thread, mediante la herencia.
  • Implementar la interfaz java.lang.Runnable.

IMPLEMENTACIÓN FRENTE A HERENCIA
La implementación de la interfaz java.lang.Runnable es la forma habitual de crear threads, ya que proporciona una forma de abstracción a la hora de definir una clase. Pero antes de continuar con la implementación de un thread debemos recordar unas cuántas diferencias entre una interfaz y una clase.
Una interfaz sólo puede contener métodos abstractos, además de variables estáticas y finales. Las clases, por otra parte, tienen la posibilidad de implementar métodos y contener variables que no sean constantes.
Una interfaz no puede implementar cualquier método. Una clase que implemente una interfaz debe implementar todos los métodos definidos en esa interfaz. Además una interfaz tiene la capacidad de heredar de otras interfaces y al contrario que las clases, puede heredar de múltiples de ellas.
Para finalizar, una interfaz no puede ser instancia del operador new.
Recordando esto, un objeto que desee utilizar los métodos que proporciona la clase java.lang.Thread se puede declarar mediante la implementación de la interfaz java.lang.Runnable, que simplemente se define de la siguiente manera descrita:

public interface Runnable {

            abstract public void run();

}

Con esto, el inicio de cada hilo quedaría definido mediante la ejecución del método run() para cada objeto creado, que contendrá el código que se va a ejecutar. Se debe destacar que run() es un método publico, no acepta parámetros y no lanza ninguna excepción. Por tanto, cualquier clase que contenga un método run() con las características anteriores, simplemente debe implementar la interfaz java.lang.Runnable. Cada objeto de esta clase sería Runnable y por tanto se podrá utilizar con la clase java.lang.Thread.

Una vez mostrados los fundamentos, pasemos a la parte práctica en la que se aprecia la sencillez de este método para generar hilos. Como se ha descrito, la clase debe implementar la interfaz Runnable, por lo que para empezar, nuestra clase debe tener la siguiente definición:

  public class miThread implements Runnable{
}

Bien, nuestro hilo empieza a ejecutarse en el método run() de la interfaz que estamos implementando, por lo que añadiremos a la clase:

public class miThread implements Runnable {

            public void run () {

                  // Código del hilo que ejecutar una vez creado

            } 

}

A partir de este momento ya hemos implementado la interfaz y el método, luego ya solamente queda poder utilizar nuestra clase como un verdadero objeto tipo de tipo Thread. Por lo tanto, generaremos un objeto tipo java.lang.Thread y le pasaremos un objeto del tipo de nuestra clase, de la forma mostrada en el ejemplo:

miThread miHilo = new miThread (); 

new Thread( miHilo ).start(); 

A partir de este momento ya hemos implementado la interfaz y el método, luego ya solamente queda poder utilizar nuestra clase como un verdadero objeto tipo de tipo Thread. Por lo tanto, generaremos un objeto tipo java.lang.Thread y le pasaremos un objeto del tipo de nuestra clase, de la forma mostrada en el ejemplo:

Es posible indicar que comience la ejecución de nuestro hilo mediante el uso de los métodos que proporciona la clase java.lang.Thread, ya que hemos generado un objeto de esta última clase pasando como argumento nuestro hilo a ejecutar.

Como se observa el método que se utiliza es start(). Cabe esperar que fuera run(), pues es el método que tiene nuestro código a ejecutar, pero no es así, el método start() es el encargado de llamar a nuestro método run(). Esto se debe a que el método start() de java.lang.Thread() arranca el objeto tipo Thread como tal, y no como un método, lo que implica a continuación la llamada al método run() de nuestra clase. En la figura 1 se puede observar gráficamente este proceso.

HERENCIA

El otro método para la creación de hilos consiste en heredar un objeto de la clase java.lang.Thread. Se trata de una forma más sencilla que la anterior, ya que la propia clase java.lang.Thread contiene el método run() que ejecuta nuestro código. Por tanto, para que nuestra clase pueda ser ejecutada como un hilo, heredamos de la clase java.lang.Thread y escribimos nuestro código dentro del procedimiento run(), que tiene las mismas características que el método run() de la interfaz Runnable. Siguiendo los pasos descritos, seremos capaces de generar un hilo heredando de la clase java.lang.Thread, tendremos:

class miHilo extends Thread {

}

Como la clase java.lang.Thread consta del método run() entonces sólo tenemos que redefinir nuestro método run() :

class miHilo extends Thread {

           public void run () {

                // Código a ejecutar 

           } 

}

La creación de nuestro hilo y su ejecución sería de la forma siguiente:

miHilo mio = new miHilo();     mio.start();

Como se observa, la ejecución también se realiza con la llamada al método start() tal y como se explicó en apartados anteriores.

La implementación de los threads mediante este método es muy interesante pero, por ejemplo, si se quiere crear un thread que herede de otra clase que no sea java.lang.Thread, como por ejemplo, Applet, no se permite. Ésto restringe considerablemente el empleo su empleo.

De los dos métodos detallados en el artículo, conceptualmente heredar de java.lang.Thread parece la mejor solución porque el objeto ya contiene el método run() y ya es de por sí (por la herencia) de la clase java.lang.Thread. Por tanto, se suele heredar de la clase java.lang.Thread cuando es posible (si nuestra clase no hereda de otras, debido a que Java no permite la herencia múltiple). Por otra parte, utilizaremos la interfaz Runnable cuando nuestra clase herede de otras, de tal forma que es el método más comúnmente utilizado.

ESTADOS DE UN THREAD

Durante el ciclo de vida de un thread, éste se puede encontrar en diferentes estados, los cuales pueden apreciarse en la figura siguiente. En ella aparecen dichos estados y los métodos que provocan el paso de uno a otro. Este diagrama no es una máquina de estados finita, pero es lo que más se aproxima al funcionamiento de un thread, que como se aprecia es muy similar al proceso clásico.

Antes de pasar a detallar los estados de un thread cabe destacar que los threads son objetos que tienen como atributo predefinido su estado de ejecución. Éste no es directamente accesible como es habitual en un atributo de cualquier objeto, sino que su estado cambia a través de los métodos disponibles por la clase java.lang.Thread.

CREACIÓN

La creación del thread se produce mediante los procedimientos anteriormente descritos, pero como se ha podido observar, su creación no implica de manera automática su ejecución, su arranque, sino que el thread pasa al estado de creado. Por ejemplo, suponiendo que creamos una clase miClaseThread que hereda de java.lang.Thread, tendríamos:

MiClaseThread miHilo = new miClaseThread();

También podríamos haber hecho:

Thread miHilo = new miClaseThread();

(Se entiende que miClaseThread hereda de java.lang.Thread y como éste es un objeto también de tipo java.lang.Thread la sentencia es correcta).

Cuando un thread se encuentra en este estado es simplemente un objeto de tipo java.lang.Thread, al que no se le asignan más recursos que la memoria necesaria. El thread esta aún sin activar, y sólo se puede arrancar usando el método start() o deternerlo definitivamente mediante el método stop(). En esta situación llamar a cualquier otro método carece de sentido y lo único que se provocaría sería el lanzamiento de una excepción de tipo IllegalThreadStateException.

ACTIVO O EJECUTABLE

Con la llamada al método start() se crearán los recursos necesarios para la que el thread pase al estado de activación, se incorporará a la lista de procesos disponibles para ser ejecutados por el sistema y se llamará al método run() de nuestro thread. En este momento el thread estará activo. Siguiendo la creación del ejemplo anterior, tendríamos:

miHilo.start();

Se denomina en estado activo y no "en ejecución" porque no se puede decir que el thread se esté ejecutando permanentemente en nuestro ordenador, es decir, que su estado sea activo no quiere decir que sé este ejecutando, aunque sí se encuentra en disposición de hacerlo.

En aquellos ordenadores con un único procesador no se pueden ejecutar n threads a la vez, por lo que para simular esta situación Java implementa un tipo de lista de prioridades que permite que el procesador sea compartido entre todos los threads que se encuentran en la lista. Gracias a la rapidez del procesador aparentemente para nosotros es como si se estuvieran ejecutando todos a la vez. Por tanto, un thread activo es aquel al que el procesador asigna regularmente un lapso o perodo de tiempo.

Cuando el thread se encuentre en este estado y se le asigne una porción de tiempo de procesador, todas las instrucciones de código que estén dentro del procedimiento run() serán ejecutadas.

DORMIDO

Un thread se encuentra en este estado si se llama al método suspend(), cuando se invoca el método sleep(), si el thread esta bloqueado por la espera de unos datos de la entrada/salida o cuando se realiza una llamada al método wait() para esperar a que se cumpla una determinada condición. En cualquiera de estas circunstancias el thread no consume recursos de la máquina.

Los métodos de recuperación o vuelta al estado activo dependerán, como es razonable, de la forma en que haya llegado al estado de dormido, siendo cada uno de ellos una forma exclusiva, por lo que tendremos:

  • Si el thread está dormido por un periodo de tiempo, volverá al estado de activo al finalizarse dicho período.
  • Si un thread esta suspendido mediante suspend(), se activa si se realiza la llamada al método resume ().
  • Si el thread esta bloqueado por una entrada/salida, cuando ésta se resuelva, automáticamente se volverá al estado activo.
  • Si el thread espera por una condición (invocación wait()), cada vez que la variable que controla la condición varíe debe llamarse a notify() o notifyAll().

Por ejemplo, un thread puede estar esperando una entrada/salida porque se requiera una entrada por teclado, con una sentencia del estilo:

System.in.read();

En nuestro procedimiento run() dormiría el hilo hasta que el usuario introdujera un dato por el teclado.

También podemos provocar la espera de un thread mediante el uso del método sleep(), por ejemplo:

class mensaje extends Thread {

                   public void run () {

                   System.out.println 
                   ("Me voy a dormir");

                   try {

                      sleep ( 20*1000 ); 

                      //tiempo en milisegundos

                   }

                   catch ( Exception e) {

                   System.out.println 
                   ("Que sueño mas raro …"); 

                        } 

                   } 

}

La línea de código sleep (20*1000) provoca que el hilo "duerma" durante veinte segundos. Durante ese tiempo transcurrido, aunque el procesador estuviera sin utilizar el thread no se ejecutaría. Tras esos segundos volverá al estado activo y entonces el procesador si le adjudicaría un lapso de tiempo cuando llegase su turno.

Este método tiene muchas aplicaciones, por ejemplo, si se quiere que el thread se active según una frecuencia de tiempo, o se lo que se desea es que el resto de hilos tengan más tiempo de ejecución, etc.

Cabe otra posibilidad es que el thread pase a dormido mediante suspend(), por lo que estaría en este estado hasta que no se ejecutara el método resume(). Por ejemplo:

class mensaje extends Thread {

               public void run () {

                    while ( true ){ 

                    System.out.println 
						( "hola hola"); 

} 

         } 

         public static void main ( String args[] ) {

         mensaje miHilo = new mensaje(); 

         miHilo.start(); 

         System.out.println 
			(" Veo muchos hola hola … ");
			System.out.println 
			(" No me acaba de gustar ");

         System.out.println 
			(" Lo siento, los paro…"); 

         miHilo.suspend(); 

         System.out.println 
			(" Que tranquilidad …");     

         System.out.println 
			(" Bueno, no están tan mal …");   

         miHilo.resume(); 

         } 

} 

El programa mezclará los mensajes "hola hola" del thread, junto con el resto de mensajes, pero cuando se ejecute el método suspend() sólo aparecerá "Que tranquilidad …" sin mezclarse con los del thread. Al llamarse al método resume() el thread vuelve a activo y continuará visualizando infinitos "hola hola".

MUERTO

El último estado en que puede estar un thread es el de muerto. A él se puede llegar de dos formas diferentes tal y como detallamos:

  • Debido a una ejecución normal o debido al empleo del método run().
  • Por la ejecución del método stop().

En el primer caso, el de la ejecución normal, la finalización proviene debido a que la tarea que se quería realizar ha finalizado,y se ha llegado al final del método run(). Por ejemplo:

public void run() {

           int veces ; 

           for ( veces = 0 ; veces < 10 ; veces ++ ){

           System.out.println (" Turno " + veces ); 

           } 

} 

Cuando se haya visualizado el número 9 el método run() finaliza, por lo que el hilo también termina.

También se puede finalizar un thread llamadon al método stop().

Si en el ejemplo de la clase mensajes se eliminan las llamadas a suspend() y resume(), y se cambian por un stop(), quedaría de la forma siguiente:

System.out.println 
(" Lo siento, los paro…"); 

// Eliminamos miHilo.suspend(); 

System.out.println 
(" Que tranquilidad …"); 

System.out.println 
(" Bueno, no están tan mal …"); 

// Eliminamos miHilo.resume(); 

miHilo.stop(); 

Al ejecutarse el método stop() se finalizaría el hilo, y no aparecerían más mensajes "hola hola" por pantalla. El hilo ha concluido de forma definitiva. Este método se encarga de enviar un objeto de tipo ThreadDeath al hilo que quiere detener, por lo que el thread morirá cuando reciba esta excepción.

Aparte de todos estos métodos descritos, existe un método que posibilita conocer en qué estado se encuentra un thread. Se trata del método isAlive(). Devuelve un valor lógico para diferenciar si el proceso se encuentra activo o dormido, por lo que obtenemos verdadero o falso si el proceso no ha sido creado o si está muerto.

Una vez que conocemos los estados de un thread y los métodos asociados, vamos a desarrollar un ejemplo en que se pueda apreciar la importancia de la programación con threads.

EJEMPLO PRÁCTICO

En el ejemplo vamos a simular una carrera entre una liebre, una tortuga y un caracol, de forma que cada uno será un objeto de una clase "animal" que tendrá la velocidad de cada animal y su nombre. Cada objeto de la clase Animal será un thread que dormirá en función de su velocidad, de tal forma que, por ejemplo, el thread asociado a la liebre dormirá menos que el del caracol.

class fabula {

        static Animal liebre ; 

        static Animal tortuga; 

        static Animal caracol ; 


public static void main( String args[] ) 
throws InterruptedException {

       liebre = new Animal ( 5, "L" ); 

       tortuga = new Animal ( 3, "T" ); 

       caracol = new Animal ( 1, "C" ); 


       liebre.start(); 

       tortuga.start(); 

       caracol.start(); 


       //El método join() consigue que hasta que no han 

       //finalizado todos hilos la carrera no se acabe 

       liebre.join(); 

       tortuga.join(); 

       caracol.join(); 

       } 

}

class Animal extends Thread{ 

      String nombre ; 

      int velocidad ; 


      public Animal ( int velocidad, String nombre ){ 

              this.nombre = nombre; 

              this.velocidad = velocidad ; 

      } 

      public void run (){

              for ( int i = 0 ; i < 10 ; i ++ ) {

              System.out.print ( nombre ); 

              try { 

              sleep ( 1000 / velocidad ); 

              } 

              catch ( Exception e ) {

              /* Nada */ ; 

              } 

       } 

       System.out.println 
		( " \t " + nombre + " llegó " ); 

       } 

} 

Como se observa, la L (liebre) al ser más rápida llega antes que los demás a la meta, tras ella la tortuga y finalmente el caracol. Con este ejemplo se ha comprobado la creación de varios hilos mediante el método de la herencia, su ejecución, el estado de dormido mediante el método sleep() y la muerte natural tras finalizar el método run(). Es un buen ejercicio realizar este mismo programa sin programación con threads.

PRIORIDADES DE LOS THREADS

Java garantiza de cierta manera la gestión de los threads en cuanto al nivel de prioridad asignado a cada uno de ellos, de tal forma que cada hilo tiene una prioridad asociada. Si en un momento determinado un thread con prioridad mayor que el thread actual pasa a estado de activo, éste pasa a ejecutarse y el actual será dormido. Dicho de otra forma, mientras el thread de mayor prioridad no esté dormido, es él el que contará con los recursos. Si se duerme, se pasa a los otros hilos la posibilidad de utilizar los recursos, pero si vuelve a pasar a activo el thread de mayor prioridad, éste recupera de nuevo los recursos. Por defecto los threads con idéntica prioridad se ejecutan con el sistema del round robin, el cual define que si un thread comienza a ejecutarse, lo seguirá haciendo hasta que ocurra una de las siguientes circunstancias:

  • Se llame al método sleep() o wait().
  • Se espere una entrada/salida.
  • Si se ejecuta un método sincronizado.
  • Se llame al método yield().
  • Se invoque el método stop().

Gráficamente este método podría describirse tal y como muestra el esquema de la figura 3.

De todos modos, Java deja ciertos aspectos del cambio de thread a la implementación que estemos utilizando. El principal problema de este asunto es que no todas las implementaciones de Java usan el mismo sistema, ya que por ejemplo el entorno Windows utiliza la técnica de time slicing (fragmento de tiempo) para los threads con la misma prioridad. Con este método, el tiempo de procesamiento de cada thread viene determinado de forma que cada hilo se ejecuta durante un período de tiempo hasta que llega el cambio de contexto, esto es, se le termina el tiempo de procesador, y se ejecuta otro hilo, aunque se siguen manteniendo las mismas reglas para los thread con mayor prioridad. Debido a que Java no garantiza la fragmentación de tiempo, todas las aplicaciones que se diseñen deben tratarse como si se fueran a ejecutar con el sistema de round robin.

TRATAMIENTO DE LAS PRIORIDADES

La prioridad por defecto de un thread está definida por la variable estática Thread.NORM_PRIORITY que tiene un valor asignado de 5.Existen otras dos variables estáticas que son Thread.MIN_PRIORITY que está fijada a 1 (menor prioridad) y finalmente Thread.MAX_PRIORITY que tiene valor 10 (de mayor prioridad).

El método disponible para asignar la prioridad de un thread es setPriority(), el cual recibe como parámetro el valor con la prioridad para el thread, por tanto, un número entre uno y diez. Si se necesita cambiar la prioridad, normalmente se incrementa en uno o dos los valores de los estándares definidos anteriormente, de forma que un pequeño incremento de prioridad es suficiente para nuestros objetivos. Por ejemplo, especificar NORMPRIORITY+1 es más que suficiente para reflejar un cambio de prioridad entre los hilos.

También puede darse la circunstancia de que necesitemos saber qué prioridad tiene un thread en un momento determinado. Para ello se dispone del método getPriority() que devuelve un número entero con el valor actual de prioridad para el thread en cuestión.

Pasemos en este momento a un ejemplo para comprobar el uso de las prioridades mediante el que se comprueba el uso del cambio, gracias a setPriority y así como el funcionamiento del método round robin cuando hay dos hilos con la misma prioridad.

class mihilo extends Thread 
{
            String mensaje; 
            mihilo ( String cadena) {
                    mensaje = cadena ; 
}
public void run() {
            while ( true ) {
                      System.out.print ( mensaje ); 
            } 
}
public static void main ( String args[] ) {
          new mihilo("K1 ").start(); 
          new mihilo("K2 ").start(); 

          mihilo obj2 = new mihilo("L "); 
          obj2.setPriority 
 ( Thread.NORM_PRIORITY+1); obj2.start(); } 
} 

Dependiendo del sistema en el que se ejecute el programa, podremos observar como resultado una salida u otra. De todos modos, cabe esperar que el hilo al que se le asigna mayor prioridad se ejecute bastantes más veces que los otros dos, ya que hasta que éste no se bloquea, pasa a dormido u otra condición no cede los recursos a los otros dos hilos de igual prioridad. Además cuando vuelva a requerir recursos los obtendrá con prioridad sobre los dos threads citados.

Los dos hilos de igual prioridad en el caso de que se aplique la fragmentación de tiempo se irán alternando en aparición, y en caso de tengamos round robin, aparecerán aleatoriamente pero no tendrán apariciones simultáneas como define el sistema.

La solución para el problema de que los thread de misma prioridad no compartan los recursos queda resuelta mediante el uso del método yield(), esto es, el thread voluntariamente cede su tiempo a otro thread, por lo que obtendremos un reparto homogéneo de los recursos entre los diferentes threads.

Volviendo al ejemplo anterior, añadiremos yield() a nuestro thread, de forma que obtendremos:

public void run() {
        while ( true ) {
                 System.out.print
			( mensaje ); yield(); 
        } 
} 

Ahora se observará que los hilos de igual prioridad aparecen alternándose el uno con el otro, cosa que no ocurriría anteriormente en implementaciones con round robin. Por ello, se recomienda siempre el uso de yield() para todos aquellos casos en que los threads tengan la misma prioridad y casi imprescindible en aquellos que realicen cálculos intensivos que requieren mucho recurso de procesador, por lo que se debería buscar un buen lugar para situar la llamada yield().

Un ejercicio interesante consiste en modificar el programa Fábula que simula una carrera entre la liebre, la tortuga y el caracol, asignando diferentes prioridades con setPriority() y situando el método yield() en diferentes lugares.

CONCLUSIONES

Como se ha podido comprobar a lo largo del artículo, el uso del lenguaje Java permite una gran flexibilidad a la hora de programar con threads.

La simplicidad para crear, ejecutar y controlar los threads permite escribir potentes aplicaciones que de otra forma serían difíciles de implementar.

En el próximo artículo comprobaremos los métodos para sincronizar threads, el uso de datos compartidos y cómo generar applets Java utilizando esta técnica entre otras muchas cosas.


Banner 468x60
Explorer 4.0, Netscape 4.0. Resolución 800 x 600.
©Tower Communications 1.998.
Diseño: GRUPO ALBERTINA DE COMUNICACION.