¿Cómo funciona java.util.concurrent.ThreadPoolExecutor en la gestión de tareas concurrentes?

| Última modificación: 15 de enero de 2025 | Tiempo de Lectura: 3 minutos

Algunos de nuestros reconocimientos:

Premios KeepCoding

Desde ya te digo que no vayas a cometer el error de creer que puedes manejar hilos en Java manualmente. Es decir, de poder se puede, pero no te lo recomiendo. Créeme cuando te digo que se te volverá un dolor de cabeza con las aplicaciones complejas. Lo mejor es que aprendas de una vez a usar java.util.concurrent.ThreadPoolExecutor, porque te servirá bastante para optimizar la ejecución de tareas concurrentes y disfrutarás más tu trabajo como desarrollador.

java.util.concurrent.ThreadPoolExecutor qué es

¿Qué es java.util.concurrent.ThreadPoolExecutor?

Piensa en un equipo de trabajo: cada miembro (hilo) tiene una tarea específica y, cuando termina, está listo para otra. El java.util.concurrent.ThreadPoolExecutor actúa como el gerente de este equipo, asignando tareas, asegurándose de que nadie esté sobrecargado y manteniendo todo bajo control. Este enfoque es más eficiente que crear y destruir hilos cada vez que los necesitas.

Como es parte del paquete java.util.concurrent, tiene configuraciones avanzadas para gestionar pools de hilos. De hecho, puedes controlar el número mínimo y máximo de hilos, el tiempo que permanecen inactivos y cómo manejar las tareas que no caben en la cola.

Lo grandioso es que, como java.util.concurrent.ThreadPoolExecutor reutiliza los hilos, se evita la sobrecarga de crear y destruirlos constantemente. Además puedes concentrarte plenamente en la lógica de tu aplicación y ajustar el tamaño de la piscina de hilos según las necesidades de tu aplicación.

Funcionamiento

  • Crea y gestiona: Es él quien decide cuántos trabajadores se necesitan en un momento dado, los contrata (crea hilos) y los despide (termina hilos) cuando ya no son necesarios.
  • Asigna tareas: Cuando llega una nueva tarea, el encargado (el Executor) busca a un trabajador disponible y le asigna la tarea.
  • Optimiza recursos: Evita crear demasiados trabajadores, lo que podría consumir demasiados recursos del sistema. También se asegura de que los trabajadores estén ocupados la mayor parte del tiempo.

Cómo usar java.util.concurrent.ThreadPoolExecutor

Para usar ThreadPoolExecutor, necesitas configurarlo con parámetros clave como el tamaño del pool, la duración de los hilos inactivos y una cola de tareas.

Veamos un ejemplo práctico que preparé para ti.

Configuración básica de un ThreadPoolExecutor

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize
4, // maximumPoolSize
10, // tiempo de inactividad para hilos extra
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2) // cola para tareas en espera
);

for (int i = 1; i <= 8; i++) {
int taskId = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " ejecutando tarea " + taskId);
try {
Thread.sleep(2000); // Simular trabajo
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}

executor.shutdown();
}
}

En este ejemplo, creé un ThreadPoolExecutor con un pool mínimo de 2 hilos y un máximo de 4. ¿Qué sucede? Si llegan más tareas de las que puede manejar, se colocan en una cola.

Salida:

pool-1-thread-1 ejecutando tarea 1
pool-1-thread-2 ejecutando tarea 2
pool-1-thread-3 ejecutando tarea 3
pool-1-thread-4 ejecutando tarea 4
pool-1-thread-1 ejecutando tarea 5
pool-1-thread-2 ejecutando tarea 6
pool-1-thread-3 ejecutando tarea 7
pool-1-thread-4 ejecutando tarea 8

Cómo personalizar fácilmente un ThreadPoolExecutor

Además de la configuración básica, puedes personalizar el comportamiento del ThreadPoolExecutor.

Por ejemplo, puedes usar un RejectedExecutionHandler para manejar tareas que no caben en la cola.

Manejo de tareas rechazadas

import java.util.concurrent.*;

public class CustomRejectionHandlerExample {
public static void main(String[] args) {
RejectedExecutionHandler rejectionHandler = (r, executor) -> {
System.out.println("Tarea rechazada: " + r.toString());
};

ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
rejectionHandler
);

for (int i = 1; i <= 10; i++) {
int taskId = i;
executor.execute(() -> System.out.println("Ejecutando tarea " + taskId));
}

executor.shutdown();
}
}

En este caso, si la cola y el pool están llenos, el RejectedExecutionHandler imprimirá un mensaje indicando que la tarea fue rechazada.

Ventajas de usar java.util.concurrent.ThreadPoolExecutor

  1. Es muy eficiente: Te conté que reutiliza hilos en lugar de crear nuevos, por eso es que se reduce el consumo de recursos.
  2. Lo puedes controlar a tu antojo: Te deja ajustar los parámetros como el tamaño del pool y la política de rechazo. Así que trabajarás sin ataduras.
  3. Flexibilidad: Te ofrece opciones para manejar colas, monitorear estados y personalizar el comportamiento.

¿Lo ves? java.util.concurrent.ThreadPoolExecutor es súper para gestionar tareas concurrentes en Java. Te permite optimizar el uso de recursos, manejar grandes volúmenes de tareas y personalizar su funcionamiento según las necesidades de tu aplicación. ¿Qué más quieres?

Si tienes potencial y disciplina, lo único que te falta es KeepCoding para alcanzar tus metas profesionales. Apúntate al Bootcamp de Java Full Stack de KeepCoding y aprende a desarrollar aplicaciones complejas y escalables desde cero. Da el siguiente paso en tu carrera y entra al emocionante mundo del desarrollo IT. ¡No dejes pasar esta oportunidad!

Posts más leídos