Cómo crear Thread Pools en Java

Un tipo de ExecutorService es el que te permite crear un Thread Pool. Este concepto ya lo abordé en un vídeo anterior, y es la forma más básica de definir un ExecutorService donde múltiples hilos pueden estar ejecutando tareas continuamente.

Cuando trabajamos con concurrencia en Java, una herramienta fundamental para gestionar múltiples tareas es el uso de pools de hilos, y para ello la clase Executors nos ofrece varias formas de crear y manejar estos pools. Vamos a explorar algunas de las opciones más comunes y entender cómo funcionan bajo el capó.

Primero, recordemos que Executors tiene muchos métodos, pero nos centraremos en aquellos que comienzan con new, ya que son los que nos permiten crear diferentes tipos de ExecutorService. Por ejemplo, el SingleThreadExecutor es un ejecutor que maneja las tareas de forma secuencial, utilizando un solo hilo. Internamente, este ejecutor es un caso especial de un pool de hilos fijo con tamaño uno, lo que significa que solo puede ejecutar una tarea a la vez y las demás quedan en cola hasta que el hilo esté disponible.

Un concepto importante que aparece en la creación de estos ejecutores es el de ThreadFactory. Esta interfaz nos permite personalizar la forma en que se crean los hilos. Por defecto, un ThreadFactory simplemente devuelve un nuevo hilo para ejecutar la tarea, pero podemos implementar uno propio para establecer prioridades, nombres específicos o cualquier otra configuración que necesitemos para los hilos que se creen.

Avanzando, uno de los métodos más utilizados es newFixedThreadPool, que crea un pool con un número fijo de hilos. Por ejemplo, si creamos un pool con 8 hilos, podremos ejecutar hasta 8 tareas simultáneamente. Si intentamos ejecutar más tareas, estas se encolarán y esperarán a que algún hilo termine su trabajo para poder ser ejecutadas. Esto es especialmente útil para limitar el uso de recursos y evitar que el sistema se sobrecargue con demasiados hilos activos.

Para ilustrar esto, imaginemos que creamos un FixedThreadPool de 8 hilos y lanzamos 8 tareas que simulan trabajo durante 2 segundos cada una. Al ejecutar el programa, veremos que las 8 tareas se ejecutan concurrentemente y terminan aproximadamente al mismo tiempo. Si en lugar de 8 hilos tuviéramos solo 1, las tareas se ejecutarían una tras otra, respetando el orden en que fueron encoladas.

También podemos ajustar el tamaño del pool según el número de procesadores disponibles en la máquina, usando Runtime.getRuntime().availableProcessors(). Esto nos ayuda a optimizar el rendimiento, adaptando el número de hilos al hardware.

Por otro lado, existe el cachedThreadPool, que es un pool de tamaño dinámico e ilimitado. Este pool crea nuevos hilos según sea necesario para ejecutar las tareas que llegan, y reutiliza los hilos que ya están disponibles. Si un hilo permanece inactivo durante un minuto, se elimina para liberar recursos. Esto significa que el pool puede crecer rápidamente si llegan muchas tareas, pero también puede reducir su tamaño cuando la carga disminuye. Internamente, este pool tiene un tamaño mínimo de cero y un máximo muy alto (Integer.MAX_VALUE), y utiliza una cola especial llamada SynchronousQueue para gestionar las tareas.

Finalmente, hay otros tipos de pools como el ScheduledThreadPool para tareas programadas, o el virtualThreadPerTask que aprovecha los hilos virtuales introducidos en Java 21, pero estos merecen una explicación aparte.

En resumen, al crear pools de hilos en Java con Executors, podemos elegir entre un número fijo de hilos para controlar la concurrencia, o un pool dinámico que se adapta a la carga de trabajo. Además, la posibilidad de personalizar la creación de hilos mediante ThreadFactory nos da un control adicional sobre el comportamiento de los hilos en nuestro sistema concurrente.

Un ejemplo básico para crear un pool fijo de 8 hilos y ejecutar tareas podría ser:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EjemploFixedThreadPool {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(8);

        for (int i = 0; i < 8; i++) {
            executor.submit(() -> {
                try {
                    System.out.println("Ejecutando tarea en hilo: " + Thread.currentThread().getName());
                    Thread.sleep(2000); // Simula trabajo
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        executor.shutdown();
    }
}

Y para un pool dinámico con cachedThreadPool:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EjemploCachedThreadPool {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();

        for (int i = 0; i < 20; i++) {
            executor.submit(() -> {
                System.out.println("Ejecutando tarea en hilo: " + Thread.currentThread().getName());
            });
        }

        executor.shutdown();
    }
}

Así, podemos adaptar la gestión de hilos a las necesidades específicas de nuestras aplicaciones concurrentes en Java.

Lista de reproducción
  1. 1
    ¿Qué es la concurrencia?
    8 minutos
  2. 2
    ¿Qué es un hilo?
    5 minutos
  3. 3
    Crear un hilo en Java
    10 minutos
  4. 4
    Interrupción de hilos
    10 minutos
  5. 5
    Cómo usar Thread.join
    14 minutos
  6. 6
    Corrupción de memoria
    8 minutos
  7. 7
    Monitores y synchronized
    10 minutos
  8. 8
    Bloque synchronized
    12 minutos
  9. 9
    Preguntas típicas sobre synchronized en Java
    8 minutos
  10. 10
    Interbloqueos, synchronized y el problema de la cena de los filósofos
    13 minutos
  11. 11
    Introducción a Executor en Java
    11 minutos
  12. 12
    Introducción al uso de ExecutorService en Java
    14 minutos
  13. 13
    Introducción a Thread Pools en Java
    10 minutos
  14. 14
    Cómo crear Thread Pools en Java
    11 minutos
  15. 15
    ¿Cómo funcionan wait() y notify()?
    10 minutos
  16. 16
    Ejemplo de wait() y notify() en Java
    9 minutos
  17. 17
    La palabra clave volatile
    8 minutos
  18. 18
    Cómo usar Future
    8 minutos
  19. 19
    Variables atómicas
    7 minutos