El concepto de procesos e hilos no es exclusivo de Java. Los sistemas operativos usan hilos para ejecutar procesos, y como mínimo un proceso debe estar siendo ejecutado en un hilo.

Manejar correctamente los hilos en cualquier aplicación informática es la diferencia más crítica a la hora de hablar de rendimiento, y por eso es necesario comprender profundamente estos conceptos como desarrolladores.

Existen fundamentalmente dos tipos de hilos en la máquina virtual de Java: los hilos del sistema y los hilos demonio (daemon). Los demonios son hilos que se ejecutan a lo largo de la vida de una aplicación controlando diversos aspectos de bajo nivel para el buen funcionamiento de los hilos del sistema. En Java, el daemon más conocido es el garbage collector, que se encarga de liberar espacio en la memoria dinámicamente buscando valores que no están siendo apuntados por ninguna variable.

Los hilos además pueden tener prioridades. Por ejemplo en Linux un proceso (que puede estás definido por uno o mas hilos), puede tener una prioridad de -20 a 20 en función de si es más o menos prioritario. En Java se maneja gracias a una propiedad de la clase Thread, que es la clase fundamental para crear nuevos hilos de sistema, en una escala de 1 a 10.

En Java existen dos maneras de crear una tarea:

  • Proveer una clase que implemente Runnable
  • Crear una clase que extienda Thread

Cada una de ellas tiene sus ventajas e inconvenientes, como explico en esta entrada, pero vemos como implementar cada una de ellas.

La interfaz Runnable

La interfaz Runnable es una interfaz funcional, es decir, una interfaz que implementa un solo método abstracto sin parámetros ni variable de retorno. Se usa para definir una tarea que deberá ejecutar un hilo.

La clase Thread

La clase Thread es la representación de un hilo, y se puede construir de dos formas.

  1. Pasándole por parámetro al constructor de la clase un objeto de tipo Runnable.
  2. Crear una clase que extienda Thread y sobrescribiendo el método run() que hereda de la interfaz Runnable.

Para lanzar el hilo, simplemente tenemos que llamar al método start(), el cual llama al método run() sobreescrito por la clase.

Veamos con un ejemplo simple esto:

Vemos que el hilo principal del programa, creado por la llamada al método main() crea dos nuevos hilos paralelos y los lanza. Podríamos representarlo gráficamente de la siguiente forma:

La máquina virtual de Java acaba la ejecución del programa cuando todos los hilos del sistema terminan.

Hay que tener muy en cuenta que la llamada al método run() de una clase que implemente Runnable no crea un nuevo hilo, solo provoca que la tarea sea ejecutada por el hilo que ha llamado al método.

En la siguiente entrada veremos brevemente cuáles son las APIs de concurrencia de Java y sus principales características.