Colas de tareas en Java: ExecutorService y ThreadPoolExecutor
En aplicaciones multihilo en Java, la gestión de tareas y su ejecución es una de las tareas clave. En lugar de crear un nuevo hilo para cada tarea (lo cual es costoso e ineficiente), los desarrolladores utilizan colas de tareas (Task Queues) en combinación con pools de hilos. Esto permite controlar la cantidad de hilos en ejecución simultánea, reutilizarlos y manejar suavemente las cargas pico.
La base para trabajar con colas de tareas en Java es el framework java.util.concurrent, presentado en Java 5. Los componentes principales son: ExecutorService, ThreadPoolExecutor y varias implementaciones de BlockingQueue.
1. ExecutorService — interfaz de alto nivel
ExecutorService es una interfaz que proporciona métodos para la ejecución asíncrona de tareas. Oculta los detalles de la gestión de hilos. La forma más popular de obtener una instancia es usar los métodos de fábrica de la clase Executors:
ExecutorService executor = Executors.newFixedThreadPool(4); // pool de 4 hilosexecutor.submit(() -> { System.out.println("Tarea ejecutándose en el hilo: " + Thread.currentThread().getName());});executor.shutdown(); // finaliza después de ejecutar todas las tareasSin embargo, los métodos de fábrica tienen capacidades de configuración limitadas. Para una configuración detallada se utiliza ThreadPoolExecutor directamente.
2. ThreadPoolExecutor — pool de hilos configurable
ThreadPoolExecutor es el núcleo del framework. Acepta una cola de tareas, una política de saturación, tiempo de vida de los hilos y otros parámetros. El constructor completo:
public ThreadPoolExecutor( int corePoolSize, // número mínimo de hilos int maximumPoolSize, // número máximo de hilos long keepAliveTime, // tiempo de inactividad del hilo antes de eliminarlo TimeUnit unit, // unidad de tiempo BlockingQueue<Runnable> workQueue, // cola de tareas RejectedExecutionHandler handler // política en caso de desbordamiento);Ejemplo de creación de un pool con una cola personalizada:
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.AbortPolicy() // por defecto — lanza una excepción);executor.execute(() -> { // tarea pesada});executor.shutdown();3. BlockingQueue — base de la cola de tareas
El tipo de cola determina cómo se almacenarán y transferirán las tareas a los hilos. Las implementaciones más populares:
LinkedBlockingQueue— cola ilimitada basada en lista enlazada. Si no se establece capacidad, puede provocar desbordamiento de memoria.ArrayBlockingQueue— cola limitada basada en array. Permite controlar la cantidad de tareas en espera.SynchronousQueue— cola de capacidad cero. Cada tarea se transfiere directamente al hilo, sin espera en la cola.PriorityBlockingQueue— cola con prioridades. Las tareas se ordenan por prioridad (implementanComparable).
Ejemplo con cola limitada y política de rechazo:
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 5, 10, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy() // la tarea se ejecuta en el hilo llamante);for (int i = 0; i < 20; i++) { executor.submit(() -> { Thread.sleep(100); return Math.random(); });}executor.shutdown();4. Políticas de saturación (RejectedExecutionHandler)
Cuando la cola se desb