Multithreading.

Vamos a comentar uno de los aspectos más interesantes del lenguaje Java: los threads o hilos de ejecución independientes y concurrentes.

Introducción. ¿qué es un thread?

La idea fundamental es bien sencilla. En la programación tradicional hay un solo flujo de control, motivado fundamentalmente porque la máquina internamente suele tener un solo procesador (una sola "mente" que realiza las instrucciones, una tras otra).

La programación multithreading permite la ocurrencia simultánea de varios flujos de control. Cada uno de ellos puede programarse independientemente y realizar un trabajo, distinto, idéntico o complementario, a otros flujos paralelos.

Hay miles de ejemplos en los que puede ser útil pensar en varios flujos de ejecución (threads): la posibilidad de editar mientras seguimos cargando o salvando un gran fichero, la posibilidad de visualizar una página mientras se están buscando las siguientes, la visualización de varios procesos que ocurren a la vez de forma independiente, etc.

Es decir, un thread será un hilo de ejecución, un proceso independiente, que se podrá ejecutar paralela o concurrentemente con otros procesos. Este thread podrá trabajar sobre datos distintos o compartidos, y podrá en un momento dado pararse, reiniciarse, sincronizarse o esperar a otros.


Ejemplo 1.

En este ejemplo crearemos una clase que será un Thread, y haremos dos instancias de esta clase para lanzar dos ejecuciones simultáneas, paralelamente, y ver qué sucede.

El código de este ejemplo se encuentra en el archivo:

\Curso Java\Java\ejemplos\curso4\EjemploThread.java

Si ejecutamos... ¿Extraño? ¿Qué es lo que ha ocurrido? Sencillo, el objeto thread1 ha ejecutado su método run() (eso es lo que hace el método start() que es el iniciador de todo thread) y ha escrito 100000 veces "PRIMERO" en su en pantalla. Pero a la vez también se ha ejecutado el run() del objeto thread2, que trataba de visualizar 100000 veces la palabra "SEGUNDO". Y obviamente no hay dos pantallas, los dos threads tienen la misma salida estándar, con lo que han ido entremezclándose las impresiones de "PRIMERO" y "SEGUNDO" por pantalla.

Para ello hemos hecho que desciendan de la clase java.lang.Thread. Cualquier subclase de Thread permite después crear objetos y ejecutar su método run() (que debe redefinirse, ya que por omisión no hace nada) en paralelo con otros.

Algunas características de la clase Thread son las siguientes:

Atributos:

Constructores: Métodos de clase: Métodos de instancia:

La interfaz Runnable.

En el ejemplo anterior vimos que para crear un Thread, un proceso que se ejecute paralelamente, hemos de heredar de la clase Thread y escribir lo que queramos que haga la ejecución de nuestro proceso en el método run. Pero, ¿qué sucede si nosotros queremos heredar de otra clase que nos interese más en lugar de la clase Thread? Como en Java no se soporta la herencia múltiple, ¿ya no podremos crear el Thread de ejecución independiente? Para solucionar este problema está la interface java.lang.Runnable, que lo único que te dice es que debes definir en tu clase un método que tenga la forma public void run(). A partir de ahí, cualquier objeto que lo haga puede usarse para crear un thread que ejecutará al iniciarse, el método run() de ese objeto. Veámoslo en un ejemplo:

Ejemplo 2.

En EjemploThread2 heredamos de Rectángulo e implementamos Runnable (para poder construirnos posteriormente un Thread con los objetos de esta clase) y definimos sobre él un método run() que será el que se ejecute cuando cree un Thread con un objeto de esta clase EjemploThread2. Vemos al ejecutarlo como se entremezclan las ejecuciones de las dos instancias que nos hemos creado.

El código de este ejemplo se encuentra en los archivos:

\Curso Java\Java\ejemplos\curso4\EjemploThread2.java
\Curso Java\Java\ejemplos\curso\Rectangulo.java


Grupos de Threads.

Mediante el uso de la clase java.lang.ThreadGroup podemos agrupar Threads. Un grupo de Threads nos será de utilidad cuando queramos coordinar un cierto número de Threads. Si agrupamos un número de Threads en un ThreadGroup, podremos hacer cosas como suspender la ejecución de todo el grupo, ejecutándo el método correspondiente sobre el grupo, que a su vez lo ejecutará sobre cada uno de los Threads miembros del grupo. Veamos un ejemplo que trate un ThreadGroup.

Ejemplo 3.

Este ejemplo crea un ThreadGroup con dos threads, al tiempo pregunta al grupo por si tiene un determinado Thread, para si es así parar su ejecución.

El código de este ejemplo se encuentra en el archivo:

\Curso Java\Java\ejemplos\curso4\EjemploThread3.java


Control de concurrencia.

Puede ocurrir que haya ciertos métodos críticos en los que los threads comparten datos o recursos que debamos controlar el correcto acceso de estos threads a estos recursos compartidos. Por ejemplo:

public void escribirUnaLinea() {
    System.out.print ("Escribo la mitad de la línea");
    System.out.println (" y ahora la finalizo");
}

Si creásemos 2 threads que ejecutasen ese código constantemente por pantalla, podría ocurrir que nuestro sistema operativo empezase a ejecutar el método sobre el primer thread y justo cuando ha ejecutado la sentencia System.out.print ("Escribo la mitad de la línea"); le pasa el control al segundo thread de modo que se entremezclarían las líneas de los dos threads pudiendo ser el resultado:

Escribo la mitad de la lineaEscribo la mitad de la línea ...

Para solucionar este problema se le pone al método el modificador synchronized, de la forma:

public synchronized void escribirUnaLinea() {
    System.out.print ("Escribo la mitad de la línea");
    System.out.println (" y ahora la finalizo");
}

de esta manera cuando un thread empieza la ejecución de un método marcado como synchronized, los demás threads no podrán entrar en ningún otro método que esté marcado como synchronized, por tanto la ejecución ahora será correcta.


Ejercicio.

Escribe una clase SumaMatriz que devuelve la suma de una matriz de doubles, creando un thread distinto para sumar cada una de sus filas, de modo que finalmente se sume el resultado de cada fila a un único total.
 
Seguir