Concurrencia en Java

Publicado: Sábado, 13 \13\UTC junio \13\UTC 2009 en 11. Concurrencia en Java

Con un único procesador, solamente puede ejecutarse una instrucción en un instante de tiempo.

La frase anterior parece una perogrullada, pero no lo es. Posiblemente estáis frente al ordenador leyendo este texto desde vuestro navegador web al mismo tiempo que escucháis el último disco de vuestro artista favorito. El ordenador está ejecutando dos programas (navegador web y reproductor de música) simultáneamente, cuando en realidad sólo puede ejecutar una instrucción – según hemos afirmado anteriormente.

Esto sucede porque el ordenador tiene tal potencia de proceso que nos da la sensación de estar realizando ambas tareas al mismo tiempo, pero simplemente se trata de una ilusión: el ordenador va intercalando la ejecución con la velocidad adecuada para ofrecernos una percepción de simultaneidad. A la capacidad de ejecutar múltiples programas a la vez se le llama multitarea (también podemos hablar de multiproceso).

Programas y procesos

A medida que avanza la tecnología, los usuarios somos más exigentes, por lo que pedimos mayor capacidad al sistema operativo para que nos permita ejecutar múltiples programas a la vez, con la mayor velocidad posible y que sea lo más cómodo para nosotros. Claro está, todo esto va complicando la programación de estos sistemas operativos y, actualmente, ya es muy difícil encontrar sistemas que no incluyan la multitarea dentro de sus características.

Veamos ahora algo de teoría sobre los procesos.

Un proceso es un programa (p.e. el archivo .class de alguno de vuestros códigos que habéis programado en Java) en ejecución. Es decir, un proceso es ese archivo .class un instante después de haber pulsado el botón Ejecutar de Eclipse. La diferencia entre programa y proceso es que el programa es algo estático (por tanto, no requiere espacio de memoria o tiempo de CPU) y el proceso es algo dinámico (se está ejecutando y requiere tiempo de CPU y almacenar su código en memoria).

Con esta situación, en sistemas operativos que nos ofrecen multitarea/multiproceso podemos ejecutar varios programas (recordad: “ejecutar varios programas” quiere decir “tener varios procesos”) a la vez.

Con esto finalizamos los conceptos teóricos. Ha sido rápido, ¿verdad? Continuemos con ejemplos prácticos.

Aunque los sistemas operativos ya gestionen correctamente la multitarea, los usuarios siguen pidiendo mayor complejidad en las aplicaciones, así que los programadores simplemente pueden echarse las manos a la cabeza y rogar que los usuarios paren de demandar mayores prestaciones o bien ponerse a trabajar en mejorar la funcionalidad de los sistemas para llegar a encontrar soluciones a las peticiones de los usuarios. Obviamente, esta última opción es la que nos interesa y es la que nos llevará a la teoría sobre los hilos. Pero antes, otro ejemplo para entender el problema.

Seguramente, alguna vez os habrá pasado que mientras estáis escuchando con el ordenador en vuestro reproductor de música un disco en formato mp3 (mejor ogg ;-)) os planteáis escucharlo en el coche. Pero el reproductor de CD reproduce únicamente CDs de audio, por lo que tenéis que convertir el disco completo a ese formato. Para ello, posiblemente el propio reproductor de música tiene esa función de conversión.

Fijaos que si podéis escuchar la música y al mismo tiempo convertir el formato, no estáis ejecutando dos procesos (ya que sólo tenéis un reproductor ejecutándose), pero sí que estáis ejecutando dos tareas a la vez.

Hilos

Uppss! ¿Esto no contradice el concepto de proceso? Habíamos dicho que un proceso=una tarea. Por tanto para tener varias tareas necesitamos ejecutar varios procesos. Pues sí y no. Esta “nueva multitarea” se debe a un nuevo concepto del que hablaremos a continuación, llamado hilo. En realidad lo que hacemos es rizar el rizo, es decir, dentro de un mismo proceso repartiremos el trabajo varios subprocesos, para que el usuario tenga la sensación (de nuevo) de estar ejecutando varias tareas a la vez dentro de un mismo programa. A esos subprocesos les llamaremos hilos.

Veamos ahora algo de teoría sobre los hilos.

Un hilo es una parte de un proceso, que tiene variables locales propias y comparte la memoria con el resto de hilos del mismo proceso.

Las programación de aplicaciones que utilizan hilos no suele ser trivial, ya que se debe controlar que el trabajo de un hilo no interfiera con el trabajo de otro hilo en un mismo proceso. Además, en ocasiones, también es necesario que los hilos se coordinen entre ellos. Una introducción a toda esta problemática y cómo se se soluciona es lo que veremos a continuación. Pero antes, seguro que te ha surgido esta pregunta…

¿Por qué utilizar multihilo en lugar de multiproceso?

Dicho de otra manera: si puedo ejecutar varias tareas utilizando varios procesos y esto es más sencillo de programar que utilizar varios hilos de un mismo proceso, ¿por qué utilizar la opción complicada? La respuesta es la rapidez., aunque no voy a extenderme en este tema. Os remito a un buen artículo de la Wikipedia sobre esto.

Creación de hilos

Vayamos al grano. Echad un vistazo a este programa:

_________________

public class UnHilo extends Thread {

public UnHilo(String nombreHilo) {

super(nombreHilo);

}

public void run() {

System.out.println(getName());

}

}

_________________

Código de UnHilo.java

_________________

public class TestUnHilo () {

public static void main (String[] args) {

UnHilo hiloUno = new UnHilo(“HiloUno”);

hiloUno.start();

}

}

_________________

Código de TestUnHilo.java

Tenemos dos clases: UnHilo y TestUnHilo. La primera contiene el código que ejecutará un hilo y la segunda crea una instancia de la primera clase (UnHilo) y la ejecuta.

Veamos cómo se hace todo esto.

Para crear un hilo existen dos formas: implementando la interfaz Runnable de Java o heredando de la clase Thread de Java. Nosotros emplearemos esta última, ya que no hemos estudiado qué es una interfaz, pero sí que sabemos qué es la herencia y cómo trabajar con ella.

Observad en el ejemplo la creación de la clase UnHilo que hereda de la clase Thread. La clase Thread de Java tiene un conjunto de métodos que nos facilitan enormemente el trabajo con hilos. En esta clase, hemos creado:

  • Un constructor con un parámetro de entrada de tipo String (que contendrá el nombre del hilo). En este constructor, simplemente llamamos al constructor de la clase base, enviando el parámetro de entrada con el nombre. Podéis ver la gran cantidad de constructores (un total de 8 que tiene la clase Thread consultando su API.
  • Un método run() que contiene el código con el trabajo que debe realizar el thread. Este es el método más importante. En nuestro caso, es muy sencillo, ya que únicamente imprime por pantalla el nombre del hilo.

Para que el hilo se ejecute, hemos incluido una clase TestUnHilo que contiene el método main(). En esta clase, podéis observar que creamos una instancia del objeto UnHilo y llamamos a su método start(). El método start() no está definido en nuestra clase, sino en la clase base Thread. Al utilizar este método, hacemos que el objeto desde el que se la llama (en nuestro caso, UnHilo) se ponga en ejecución.

El método start() hace una serie de procesos (que son transparentes para nosotros) e invoca al método run() del hilo.

Con esto ya sabemos implementar la creación de hilos mediante la clase Thread.

Terminación de un hilo

Un hilo finalizará cuando termine la ejecución de su método run(). Una vez finaliza, ya no se puede volver a ejecutar. Al estado que se encuentra cuando finaliza se le llama muerto (dead). A continuación, veréis qué otros estados puede tener un proceso.

Estados de un hilo

Un hilo tiene su propio ciclo de vida, durante el cual puede encontrarse en diferentes estados. Java controla el estado de los hilos mediante el llamado planificador de hilos, que se encargará de gestionar qué hilo debe ejecutarse en cada momento y en qué estado deben encontrarse el resto de hilos.

Observad esta imagen.

Ciclo de vida de un proceso

En ella podéis ver un esquema de los diferentes estados en los que puede encontrarse un hilo (representados en un recuadro) y qué métodos pueden llevar ese estado al hilo (representados por flechas).

¿Qué significan cada uno de estos estados? Veámoslo.

  • Nuevo. Se ha creado un objeto hilo, pero todavía no se le ha asignado ninguna tarea. Para ello, se ha de llamar a su método start() y el hilo pasará al estado preparado.
  • Preparado. El hilo está preparado para ejecutarse, pero el planificador de hilos es quién debe decidir si puede hacerlo o debe esperar (por ejemplo, a que acabe la ejecución otro hilo).
  • En ejecución. Una vez el hilo puede acceder a tiempo de CPU, se ejecutará. Si el hilo finaliza su trabajo completamente, pasará al estado muerto. Si el planificador de hilos decide que ha cumplido su periodo de tiempo y el hilo no ha finalizado su trabajo, pasará al estado preparado y esperará a que el planificador de hilos vuelva a darle permiso para ejecutarse.
  • Bloqueado. El hilo no puede ejecutarse porque espera que ocurra algo. En cuanto ocurra lo que le está dejando bloqueado, pasará al estado preparado.
  • Muerto. El hilo ha finalizado su tarea y deja de existir.

Explicaremos en una próxima entrada cómo se gestionan cada uno de estos estados.


Creative Commons License
Concurrencia en Java by Cristian Jorge Garcia Marcos is licensed under a Creative Commons Reconocimiento-Compartir bajo la misma licencia 3.0 España License.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s