Ejemplos

Ejemplo 06: Manejando Hilos de formas Diferentes en Java

En este ejemplo, exploraremos diferentes formas de manejar hilos en Java, incluyendo la creación de hilos utilizando la clase `Thread`, la interfaz `Runnable`, y el uso de las funciones `sleep`, `join` e interrupciones para controlar la ejecución de los hilos.

En este ejemplo, vamos a explorar diferentes formas de manejar hilos en Java. Veremos cómo crear hilos utilizando la clase Thread y la interfaz Runnable, así como el uso de las funciones sleep, join e interrupciones para controlar la ejecución de los hilos.

Descripción del Problema

Supongamos que quieres crear un programa que simule la ejecución de varias tareas concurrentes, como la impresión de mensajes en la consola. Queremos que cada tarea se ejecute en un hilo separado y que el programa principal espere a que todas las tareas se completen antes de finalizar.

Código de Resolución

Para resolver este problema, crearemos una clase Task que implementa la interfaz Runnable. Esta clase representará una tarea que se ejecutará en un hilo separado. Luego, crearemos una clase ThreadManager que manejará la creación y ejecución de los hilos para cada tarea. Posteriormente, utilizaremos las funciones sleep para simular el tiempo de ejecución de cada tarea, join para esperar a que los hilos terminen su ejecución y interrupt para manejar posibles interrupciones. Para finalizar, implementaremos un Función para mostrar el progreso de las tareas.

Paso 1: Crear la clase Task

package app.concurrencia.entities;

/**
 * Clase que representa una tarea que se ejecutará en un hilo separado.
 */
public class Task implements Runnable {
    /**
     * Nombre de la tarea.
     */
    private String taskName;

    /**
     * Constructor de la clase Task.
     * 
     * @param taskName Nombre de la tarea.
     */
    public Task(String taskName) {
        this.taskName = taskName;
    }

    /**
     * Función que se ejecuta cuando el hilo inicia.
     */
    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(taskName + " - Iteración " + (i + 1));
                Thread.sleep(1000); // Simula el tiempo de ejecución de la tarea
            }
        } catch (InterruptedException e) {
            System.out.println(taskName + " ha sido interrumpida.");
        }
    }
}

Paso 2: Crear la clase ThreadManager

package app.concurrencia.entities;

import java.util.ArrayList;
import java.util.List;

/**
 * Clase que maneja la creación y ejecución de hilos para cada tarea.
 */
public class ThreadManager {
    private List<Thread> threads;

    public ThreadManager() {
        threads = new ArrayList<>();
    }

    /**
     * Función para iniciar las tareas en hilos separados.
     * 
     * @param tasks Lista de tareas a ejecutar.
     */
    public void startTasks(List<Task> tasks) {
        for (Task task : tasks) {
            Thread thread = new Thread(task);
            threads.add(thread);
            thread.start();
        }
    }

    /**
     * Función para esperar a que todas las tareas se completen.
     */
    public void waitForTasks() {
        for (Thread thread : threads) {
            try {
                thread.join(); // Espera a que el hilo termine su ejecución
            } catch (InterruptedException e) {
                System.out.println("El hilo ha sido interrumpido.");
            }
        }
    }

    /**
     * Función para interrumpir todas las tareas.
     */
    public void interruptTasks() {
        for (Thread thread : threads) {
            thread.interrupt(); // Interrumpe el hilo
        }
    }

    /**
     * Función para mostrar el progreso de las tareas.
     */
    public void showProgress() {
        for (Thread thread : threads) {
            if (thread.isAlive()) {
                System.out.println(thread.getName() + " está en ejecución.");
            } else {
                System.out.println(thread.getName() + " ha finalizado.");
            }
        }
    }
}

Paso 3: Crear la clase Main

package app.concurrencia.launch;

import app.concurrencia.entities.Task;
import app.concurrencia.entities.ThreadManager;
import java.util.Arrays;

public class Main {
    void main() {
        ThreadManager threadManager = new ThreadManager();

        // Crear una lista de tareas
        Task task1 = new Task("Tarea 1");
        Task task2 = new Task("Tarea 2");
        Task task3 = new Task("Tarea 3");

        // Iniciar las tareas en hilos separados
        threadManager.startTasks(Arrays.asList(task1, task2, task3));

        // Mostrar el progreso de las tareas
        threadManager.showProgress();

        // Esperar a que todas las tareas se completen
        threadManager.waitForTasks();

        // Mostrar el progreso final de las tareas
        threadManager.showProgress();
    }
}

Salida Esperada

Cuando ejecutes el programa, deberías ver una salida similar a la siguiente en la consola:

Tarea 1 - Iteración 1
Tarea 2 - Iteración 1
Tarea 3 - Iteración 1
Tarea 1 - Iteración 2
Tarea 2 - Iteración 2
Tarea 3 - Iteración 2
Tarea 1 - Iteración 3
Tarea 2 - Iteración 3
Tarea 3 - Iteración 3
Tarea 1 - Iteración 4
Tarea 2 - Iteración 4
Tarea 3 - Iteración 4
Tarea 1 - Iteración 5
Tarea 2 - Iteración 5
Tarea 3 - Iteración 5
Tarea 1 ha finalizado.
Tarea 2 ha finalizado.
Tarea 3 ha finalizado.

En esta salida, cada tarea se ejecuta en un hilo separado, y el programa principal espera a que todas las tareas se completen antes de finalizar. Además, se muestra el progreso de las tareas antes y después de la ejecución.

Terminar las tareas por interrupción

Si deseas terminar las tareas antes de que completen su ejecución, puedes llamar a la función interruptTasks() del ThreadManager. Esto interrumpirá todos los hilos en ejecución, y cada tarea manejará la interrupción mostrando un mensaje en la consola.

// Interrumpir todas las tareas
threadManager.interruptTasks();

Al ejecutar esta línea, verás mensajes similares a los siguientes en la consola:

Tarea 1 ha sido interrumpida.
Tarea 2 ha sido interrumpida.
Tarea 3 ha sido interrumpida.

Terminando las tareas por ráfagas

Recordemos que la función join cuenta con una sobrecarga que permite especificar un tiempo máximo de espera para que un hilo termine su ejecución. Si el hilo no termina dentro de ese tiempo, el programa continúa con la siguiente línea de código. Esto es útil para evitar que el programa se quede bloqueado esperando a que un hilo termine su ejecución.

Entonces, usemos esto para esperar a que las tareas terminen su ejecución por ráfagas, es decir, esperando un tiempo máximo para cada tarea antes de continuar con la siguiente tarea, interrumpiendo las tareas que no hayan terminado dentro de ese tiempo máximo y agregando nuevamente la tarea a la lista de tareas para esperar nuevamente en la siguiente ráfaga.

Para esto, modificaremos la función waitForTasks del ThreadManager de la siguiente manera:

public void waitForTasks(int maxWaitTime) {
    List<Thread> remainingThreads = new ArrayList<>(threads);
    while (!remainingThreads.isEmpty()) {
        List<Thread> threadsToRemove = new ArrayList<>();
        for (Thread thread : remainingThreads) {
            try {
                thread.join(maxWaitTime); // Espera un tiempo máximo para que el hilo termine su ejecución
                if (!thread.isAlive()) {
                    threadsToRemove.add(thread); // Si el hilo ha terminado, lo agregamos a la lista de hilos a eliminar
                } else {
                    thread.interrupt(); // Si el hilo no ha terminado, lo interrumpimos
                }
            } catch (InterruptedException e) {
                System.out.println("El hilo ha sido interrumpido.");
            }
        }
        remainingThreads.removeAll(threadsToRemove); // Eliminamos los hilos que han terminado de la lista de hilos restantes
    }
}

En esta función, se espera un tiempo máximo para cada hilo antes de continuar con la siguiente tarea. Si un hilo no termina dentro de ese tiempo, se interrumpe y se agrega nuevamente a la lista de tareas para esperar nuevamente en la siguiente ráfaga. Este proceso se repite hasta que todas las tareas hayan terminado su ejecución. Para que esto funcione, también debemos modificar la función main de la clase Main para llamar a esta nueva función waitForTasks con un tiempo máximo de espera, por ejemplo, 2000 milisegundos:

// Esperar a que todas las tareas se completen por ráfagas
threadManager.waitForTasks(2000);

Con esta implementación, el programa esperará a que cada tarea termine su ejecución por ráfagas, interrumpiendo las tareas que no hayan terminado dentro del tiempo máximo de espera y continuando con la siguiente tarea. Esto permite un manejo más eficiente de las tareas concurrentes, evitando que el programa se quede bloqueado esperando a que una tarea termine su ejecución.

Conclusión

En este ejemplo, hemos explorado diferentes formas de manejar hilos en Java, incluyendo la creación de hilos utilizando la clase Thread y la interfaz Runnable, así como el uso de las funciones sleep, join e interrupciones para controlar la ejecución de los hilos. Hemos visto cómo crear tareas que se ejecutan en hilos separados, cómo esperar a que las tareas terminen su ejecución por ráfagas y cómo interrumpir las tareas de manera adecuada. Este enfoque nos permite manejar eficientemente las tareas concurrentes en Java, evitando bloqueos y permitiendo un control más preciso sobre la ejecución de los hilos.

Copyright Jesús Aurelio Castro Magaña © 2026