¿Cómo implementar java.util.concurrent.ExecutorService en tus proyectos?

| Última modificación: 7 de enero de 2025 | Tiempo de Lectura: 4 minutos

Algunos de nuestros reconocimientos:

Premios KeepCoding

Tremendo me pareció a mí trabajar con aplicaciones multihilo en Java. Crear hilos manualmente, asignarles tareas y gestionarlos me resultó tedioso y me arrojó varios errores. Si estás en esa misma situación yo te contaré cómo salir de callejón que parece no tener salida. Lo que a mí me sirvió fue aprender a usar java.util.concurrent.ExecutorService, porque logré simplificar el manejo de hilos en mis proyectos y le he sacado bastante provecho. Así que te contaré cómo implementarla fácilmente para que tu vida como desarrollador sea mucho más tranquila.

java.util.concurrent.ExecutorService qué es en Java

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

Lo que se me ocurre es que, puedes pensar en los hilos de Java como si fueran empleados. Si te da por asignarles tareas a cada uno, de forma manual, probablemente te volverás loco. Entonces, java.util.concurrent.ExecutorService en este caso sería como un gestor que va a distribuir el trabajo entre tus empleados (hilos) de manera ordenada, optimizando los recursos disponibles.

En otras palabras, esta interfaz de Java simplifica la ejecución de tareas asíncronas utilizando un pool de hilos. Este pool gestiona la creación, reutilización y finalización de hilos, lo que reduce la complejidad de trabajar directamente con la clase Thread.

Características

  • Gestión automática de hilos: Olvídate de crear y destruir hilos manualmente.
  • Escalabilidad: Optimiza el uso de recursos en aplicaciones de alto rendimiento.
  • Flexibilidad: Admite tareas concurrentes con resultados, excepciones y tiempo límite

Cómo crear un ExecutorService

Usar la clase Executors

La forma más común de crear un ExecutorService es utilizando métodos de la clase Executors. Aquí tienes algunos ejemplos:

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

public class CrearExecutor {
public static void main(String[] args) {
// Pool de 5 hilos
ExecutorService poolFijo = Executors.newFixedThreadPool(5);

// Un solo hilo
ExecutorService poolSecuencial = Executors.newSingleThreadExecutor();

System.out.println("Pools creados con éxito");
}
}

Crear instancias directamente

Si necesitas un control más avanzado, puedes usar implementaciones directas como ThreadPoolExecutor:

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

public class CrearThreadPool {
public static void main(String[] args) {
ExecutorService customPool = new ThreadPoolExecutor(
4, // Hilos mínimos
8, // Hilos máximos
60, // Tiempo de espera
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);

System.out.println("Custom ThreadPool creado");
}
}

Cómo ejecutar tareas con java.util.concurrent.ExecutorService

Método execute

Si solo necesitas ejecutar tareas sin preocuparte por el resultado, utiliza execute:

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

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

Runnable tarea = () -> {
System.out.println("Tarea ejecutada por: " + Thread.currentThread().getName());
};

executor.execute(tarea);
executor.shutdown();
}
}

Método submit

Para obtener el resultado de una tarea, lo mejor es que uses submit. Este método devuelve un Future que puedes usar para recuperar el valor calculado:

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

public class UsarSubmit {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);

Future<String> resultado = executor.submit(() -> {
Thread.sleep(1000);
return "Resultado calculado";
});

System.out.println("Resultado: " + resultado.get());
executor.shutdown();
}
}

Cómo ejecutar múltiples tareas

invokeAny: Aprende a obtener el resultado de la primera tarea completada

Algo muy bueno es que, si tienes varias tareas y solo te interesa el resultado de la que termine primero, puedes hacerlo así:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class UsarInvokeAny {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(3);

List<Callable<String>> tareas = new ArrayList<>();
tareas.add(() -> {
Thread.sleep(1000);
return "Tarea 1 terminada";
});
tareas.add(() -> "Tarea 2 terminada");

String resultado = executor.invokeAny(tareas);
System.out.println("Primer resultado: " + resultado);
executor.shutdown();
}
}

invokeAll: Cómo obtener los resultados de todas las tareas

Cuando necesitas ejecutar varias tareas y recuperar los resultados de todas, yo te aconsejo hacer esto:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class UsarInvokeAll {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(3);

List<Callable<String>> tareas = new ArrayList<>();
tareas.add(() -> "Resultado 1");
tareas.add(() -> "Resultado 2");

List<Future<String>> resultados = executor.invokeAll(tareas);

for (Future<String> resultado : resultados) {
System.out.println("Resultado: " + resultado.get());
}

executor.shutdown();
}
}

Cómo finalizar un ExecutorService

Ten en cuenta que un ExecutorService no se cierra automáticamente después de ejecutar tareas. Puedes cerrarlo manualmente con shutdown o shutdownNow:

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

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

executor.execute(() -> System.out.println("Ejecutando tarea"));

executor.shutdown(); // Permite terminar las tareas en curso
// executor.shutdownNow(); // Intenta detener inmediatamente
}
}

Me alegra que ahora sepas que java.util.concurrent.ExecutorService es una herramienta esencial para trabajar con concurrencia en Java. Simplifica la gestión de hilos y mejora el rendimiento de tus aplicaciones.

¿Te interesa aprender más sobre programación concurrente y otras herramientas de Java? En nuestro Bootcamp de Java Full Stack, aprenderás todo lo necesario para destacar en el sector IT. ¡Apúntate hoy y comienza a construir tu futuro!

Posts más leídos