Durante la ejecución de los programas existen muchas operaciones que precisan de una espera; en busca de una interacción con el exterior, dejando pasar el tiempo, esperando a que otro proceso acabe...
Java permite que estos tiempos desaprovechados sean utilizados por el programador para realizar determinadas tareas, y así aprovechar el microprocesador durante toda la ejecución del programa. Para ello implementa el concepto de threads, o hilos de control del programa.
Mediante el uso de varios threads, se consigue ejecutar varios procesos en paralelo, de forma que cuando uno de ellos esté esperando algún evento, permita que el microprocesador ejecute alguno de los otros threads en espera. Cuando el evento que el primer thread esperaba sucede, de nuevo se intercambian los threads para que el primer thread continúe su ejecución.
Todo esto viene a suplir a la técnica de exclusión mutua denominada utilización de semáforos, extendida entre los programadores de C en UNIX.
B. Utilización de threadPara crear un thread, se ha de implementar una clase, extendiendo la clase base Runnable, y crear un objeto de la clase Thread. Este objeto representará un nuevo hilo de control, que será accionado cuando invoquemos al método start() del thread. En ese momento este hilo se activará, ejecutando (si el planificador de hilos considera que es el momento), el método run() de la clase en que todo esto suceda.
Por ejemplo, el siguiente programa utiliza dos hilos, el hilo general main, y el hilo thDemo que creamos:
import java.io.*; import java.net.*; class ThreadDemo implements Runnable { ThreadDemo() { Thread thDemo = new Thread( this, "ThDemo" ); thDemo.start(); } public void run() { try { Thread.sleep(3000); } catch( InterruptedException e ) { } System.out.println("Saliendo del hilo hijo"); } public static void main( String args[] ){ new ThreadDemo(); try { for ( int i = 5 ; i >0 ; i-- ) { System.out.println(" Seg: " + i ); Thread.sleep(1000); } } catch( InterruptedException e ) { } System.out.println("Saliendo del main"); } }
Ambos hilos esperan utilizando el método sleep() de la clase Thread; thDemo tres segundos, y main cinco segundos. Java utilizará los tres segundos de thDemo para ir esperando los tres primeros segundos del hilo main.
Por lo tanto la salida por pantalla al ejecutar el programa es:
prompt> java ThreadDemo Seg: 5 Seg: 4 Seg: 3 Saliendo del hilo hijo Seg: 2 Seg: 1 Saliendo del hilo main C. Sincronización de threadsDurante la ejecución de un programa, muchas veces varios procesos han de realizar tareas de una forma sincronizada, actuando en un determinado orden. Para ello en Java se utilizan la palabra reservada syncronized, en la declaración de los procesos con este tipo de características.
Los procesos declarados como syncronized mediante la utilización de excepciones, y de las funciones wait() y notifiy(), respectivamente esperarán a que otro proceso acabe antes de continuar su ejecución.
A continuación se va a ir viendo cómo implementar el clásico problema de exclusión mutua conocido como el problema del productor/consumidor, en el que dos procesos han de acceder a una cola común, en la que el proceso productor inserta elementos en la pila, y el proceso consumidor ha de ir consumiendo los elementos en la pila, cada vez que sean insertados:
class ColaSincronizada { int n; boolean bandera = false; synchronized int obten() { if ( !bandera ) try wait(); catch( InterruptedException e ); System.out.println( "Obtenido: " + n ); bandera = false; notify(); return n; } synchronized void coloca( int paramN ) { if ( bandera ) try wait(); catch( InterruptedException e ); n = paramN; bandera =true; System.out.println( "Colocado: " + n ); notify(); } } class Productor implements Runnable { ColaSincronizada colaProductor; Productor( ColaSincronizada paramCola ) { colaProductor = paramCola; new Thread( this, "Producer" ).start(); } public void run() { int i = 0; while ( true ) // Bucle infinito colaProductor.coloca( i++ ); } } class Consumidor implements Runnable { ColaSincronizada colaConsumidor; Consumidor( ColaSincronizada paramCola ) { colaConsumidor = paramCola; new Thread( this, "Consumidor" ).start(); } public void run() { while ( true ) // Bucle infinito colaConsumidor.obten( ); } } public static void main( String args[] ) { ColaSincronizada colaLocal = new ColaSincronizada(); new Productor( colaLocal ); new Consumidor( colaLocal ); }
La salida del programa será:
Colocado: 1 Obtenido: 1 Colocado: 2 Obtenido: 2 Colocado: 3 Obtenido: 3 . . . D. Y mucho másLa utilización de programación concurrente y de los hilos de Java con toda su potencia va mucho más allá de los objetivos de este tutorial. Lo que aquí se ha visto es simplemente una introducción para que el lector sea consciente de cuál es la potencia de este tipo de programación.
La utilización de los threads se extiende con métodos para que el programador controle la alternancia de los hilos. Estos métodos son:
Para más información sobre los threads véase [Zolli, 1997].