Java en hebras

La clase anterior usamos, en el último ejemplo, un concepto al que vamos a dedicar ahora nuestra atención: los threads.

La traducción literal de thread es hilo o hebra, y se utiliza también para referirse al hilo de un discurso. El concepto de threads en los ambientes y sistemas operativos es un poco complejo de explicar pero sencillo de entender: independientemente del sistema elegido, puede pensarse que un thread es algo así como el lugar de ejecución de un programa.

En la mayoría de los programas que hemos visto, hemos usado un solo thread; es decir que un programa comienza y su ejecución sigue un camino único: como un monólogo.

Java es multithreading. Esto significa algo así como que tiene capacidad de diálogo, y más aún: puede ejecutar muchos threads en paralelo, como si tratáramos de una conversación múltiple y simultánea.

No confundir aquí multithreading con la capacidad de ejecutar varios programas a la vez. Esta es una posibilidad, pero también un mismo programa puede utilizar varios threads ("caminos de ejecución"?) simultáneamente.

Esto, por supuesto, depende fundamentalmente de la capacidad del sistema operativo para soportar multithreading, y por esto Java no puede ejecutarse (al menos en forma completa) en sistemas que no lo soporten.

El uso de threads nos permite, por ejemplo, ejecutar simultáneamente varios programas que interactúen entre ellos; o, también, que un programa, mientras por ejemplo actualiza la pantalla, simultáneamente realice una serie de cálculos sin tener que hacer esperar el usuario.

Una forma sencilla de verlo es imaginar que tenemos un grupo de microprocesadores que pueden ejecutar, cada uno, un solo thread; y nosotros asignamos programas (o partes de programas) a cada uno de ellos. Además, podemos imaginar que esos microprocesadores comparten una memoria común y recursos comunes, de lo que surgirá una serie de problemas importantes a tener en cuenta cuando se usan threads.

Los pasos básicos

Hay tres cosas a tener en cuenta para usar threads en un programa:

La clase que queremos asignar a un thread debe implementar la interface Runnable.
Debemos crear una variable (instancia) del tipo Thread, que nos permitirán acceder y manejar el thread. En los applets, en el método start() simplemente crearemos el thread (y, posiblemente, lo pondremos a ejecutar)
Y por último tenemos que crear un método run() que es el que ejecuta el código del programa propiamente dicho.

La interface Runnable, simplemente definida como:

public  interface  java.lang.Runnable
{
        // Methods
    public abstract void run();
}

le asegura al compilador que nuestra clase (la que utilizará el thread para ejecutarse) dispone de método run().

Vamos a ver un par de ejemplos, primero una aplicación standalone y luego un applet.

Reunión de amigos

El siguiente ejemplo (Ejemplo19.java) usa threads para activar simultáneamente tres objetos de la misma clase, que comparten los recursos del procesador peleándose para escribir a la pantalla.

class Ejemplo19 {

	public static void main(String argv[])
	throws InterruptedException {
		Thread Juan = new Thread (new Amigo("Juan"));
		Thread Luis = new Thread (new Amigo("Luis"));
		Thread Nora = new Thread (new Amigo("Nora"));
		Juan.start();
		Luis.start();
		Nora.start();
		Juan.join();
		Luis.join();
		Nora.join();
	}

}

class Amigo implements Runnable {

	String mensaje;

	public Amigo(String nombre) {
		mensaje = "Hola, soy "+nombre+" y este es mi mensaje ";
	}

	public void run() {
		for (int i=1; i<6; i++) {
			String msg = mensaje+i;
			System.out.println(msg);
		}
	}

}


Como siempre, compilarlo con javac Ejemplo19.java y ejecutarlo con java Ejemplo19.

En un sistema operativo preemptivo, la salida será más o menos así:

Hola, soy Juan y este es mi mensaje 1
Hola, soy Juan y este es mi mensaje 2
Hola, soy Luis y este es mi mensaje 1
Hola, soy Luis y este es mi mensaje 2
Hola, soy Nora y este es mi mensaje 1
Hola, soy Nora y este es mi mensaje 2
Hola, soy Nora y este es mi mensaje 3
Hola, soy Juan y este es mi mensaje 3
...........etc.


Qué significa que un sistema operativo es preemptivo? Casos típicos son Unix o Windows 95: cada tarea utiliza una parte del tiempo del procesador, y luego lo libera para que puedan ejecutarse otras tareas (otros threads). Por eso se mezclan los mensajes de salida. Si el sistema operativo es no preemptivo, el procesador no se libera hasta que no termina con el thread actual, y por lo tanto la salida sería así:

Hola, soy Juan y este es mi mensaje 1
Hola, soy Juan y este es mi mensaje 2
Hola, soy Juan y este es mi mensaje 3
Hola, soy Juan y este es mi mensaje 4
Hola, soy Juan y este es mi mensaje 5
Hola, soy Luis y este es mi mensaje 1
Hola, soy Luis y este es mi mensaje 2
...........etc.


Si ustedes están utilizando un sistema operativo no preemptivo, deben explícitamente indicarle al procesador cúando puede ejecutar (dar paso) a otra tarea; para eso simplemente modifiquen el método run():

	public void run() {
		for (int i=1; i<6; i++) {
			String msg = mensaje+i;
			System.out.println(msg);
			Thread.yield();
		}
	}


En este ejemplo, tanto en sistemas preemptivos como no preemptivos la salida será:

Hola, soy Juan y este es mi mensaje 1
Hola, soy Luis y este es mi mensaje 1
Hola, soy Nora y este es mi mensaje 1
Hola, soy Juan y este es mi mensaje 2
Hola, soy Luis y este es mi mensaje 2
Hola, soy Nora y este es mi mensaje 2
Hola, soy Juan y este es mi mensaje 3
Hola, soy Luis y este es mi mensaje 3
...........etc.


Esto es porque en seguida de imprimir estamos liberando al procesador para que pase a otro thread (si hay alguno esperando). Noten la diferencia con el primer caso, sin usar yield(), para sistemas preemptivos: el procesador reparte su trabajo en forma (aparentemente) impredecible, por eso el orden de los mensajes no será el mismo en cualquier máquina o sistema operativo.

Ya lo vimos funcionar, pero sería bueno que lo entendamos! Por eso, vamos paso a paso.

Creando Threads

Thread es una clase básica en Java, que implementa la interface Runnable y dispone de unos cuantos métodos por defecto. Lo importante a tener en cuenta que, para usar Threads, debemos crearlas como instancias y ponerlas a "andar":

		Thread Juan = new Thread (new Amigo("Juan"));
		..............
		Juan.start();
		..............
		Juan.join();

Un thread tiene cuatro estados posibles:

creado: ha sido creado mediante new(), pero no se ha puesto en marcha todavía.

activo: está en ejecución, ya sea porque arrancó con start() o fue "despertado" con resume().

dormido: ha sido suspendida su ejecución momentáneamente mediante wait(), sleep() o suspend().

muerto: se ha detenido definitivamente, ya sea porque se terminó el programa o mediante el llamado a stop().

En este ejemplo hemos creado un thread asignándole simultáneamente un objeto que lo utiliza (new Amigo("Juan")), y seguidamente lo hemos activado, llamando al método start(). Este método se encarga de inicializar el thread y, finalmente, llamar al método run() que hemos implementado.

De este modo, todo ocurre como si los métodos run() de cada objeto se ejecutaran en paralelo, concurrentemente. La forma de manejar esto depende del sistema operativo.

El método join() que llamamos al final hace que el programa principal espere hasta que este thread esté "muerto" (finalizada su ejecución). Este método puede disparar la excepción InterruptedException, por lo que lo hemos tenido en cuenta en el encabezamiento de la clase.

En nuestro ejemplo, simplemente a cada instancia de Amigo(...) que creamos la hemos ligado a un thread y puesto a andar. Corren todas en paralelo hasta que mueren de muerte natural, y también el programa principal acaba.

Cuando usamos Thread.yield() (que en rigor debería ser Thread.currentThread().yield(), pero siendo algo de uso muy común los desarrolladores de Java lo han simplificado), simplemente el thread actual le permite al procesador dedicarse a otro (si es que hay alguno deseando utilizar sus servicios).

La clase Amigo() es muy simple y con lo que hemos visto hasta ahora no creo que tengamos que explicar nada más.

Y los applets...?

También podemos usar estos conceptos en los applets. Veamos un ejemplo para terminar la clase de hoy, muy similar al anterior, donde tres contadores cuentan (en un sistema preemptivo) en forma simultánea. Recuerden crear una página HTML con el tag

<applet code="Ejemplo20.class" width=300 height=100></applet>

para poder verlo en acción con el appletviewer o su browser favorito (que desde ya supongo que soporta Java! ;-)

El programa es extremandamente sencillo, y pueden verlo en acción si lo desean cargando via Internet la página:

http://www.amarillas.com/rock/java/Ejemplo20.htm

//	Ejemplo de applet que usa multithreading
import java.awt.*;
import java.applet.*;

public class Ejemplo20 extends Applet {

	TextField tfa,tfb,tfc;

	public void init() {
		setLayout(new GridLayout(3,2));
		tfa = new TextField("0");
		tfb = new TextField("0");
		tfc = new TextField("0");
		add(new Label("Contador A"));
		add(tfa);
		add(new Label("Contador B"));
		add(tfb);
		add(new Label("Contador B"));
		add(tfc);
	}

	public void start() {
		Thread A = new Thread (new Counter(tfa));
		Thread B = new Thread (new Counter(tfb));
		Thread C = new Thread (new Counter(tfc));
		A.start();
		B.start();
		C.start();
	}

}

class Counter implements Runnable {

	TextField texto;
	String s;

	public Counter(TextField txtf) {
		texto = txtf;
	}

	public void run() {
		for (int i=0; i<1000; i++) {
			texto.setText(s.valueOf(i));
		}
	}

}




Disculpen la demora, pero estaba muy ocupado! Nos vemos la próxima clase.

<> Página de tutoriales <> Página Anterior <> Siguiente Capitulo <>