En el desarrollo de aplicaciones empresariales con Spring Boot, es frecuetne enfrentar escenarios donde se debe iterar sobre una lista de registros y, por cada uno, realizar una operación costosa, como una petición a una API de terceros. Si estas operaciones se ejecutan de forma secuencial, el tiempo de respuesta total aumenta proporcionalmente al número de elementos, degradando la experiencia del usuario.
Para solventar esto, se suele recurrir a la anotación @Async. Sin embargo, el uso simple de la asincronía presenta un reto: el hilo principal puede finalizar su ejecución y retornar una respuesta antes de que las tareas en segundo plano completen la carga de datos. Para coordinar estos hilos y asegurar que el hilo principal espere a los procesos paralelos, la clase CountDownLatch del paquete java.util.concurrent es una solución robusta.
Definición del componente asíncrono
Primero, definimos un servicio encargado de la ejecución en paralelo. Es fundamental pasar el objeto CountDownLatch como parámetro para que cada hilo pueda notificar su finalización. Se recomienda el uso de un bloque try-finally para garantizar que el contador decremente incluso si ocurre una excepción.
@Component
public class ProcesadorAsincrono {
@Async("taskExecutor")
public void ejecutarTarea(DatosRegistro registro, CountDownLatch sincronizador) {
try {
// Simulación de llamada a API externa y asignación de datos
String resultadoExterno = invocarServicioExterno(registro.getId());
registro.setDetalle(resultadoExterno);
} catch (Exception e) {
// Manejo de errores específico
} finally {
// Notifica que el hilo ha terminado
sincronizador.countDown();
}
}
private String invocarServicioExterno(Long id) {
// Lógica de comunicación HTTP
return "Info Recuperada";
}
}
Gestión de la orquestación en la capa de Servicio
En el servicio principal, inicializamos el CountDownLatch con un contador igual al tamaño de la lista de elementos a procesar. El hilo principal se bloqueará en el método await() hasta que el contador llegue a cero.
@Service
public class RegistroService {
@Autowired
private ProcesadorAsincrono procesador;
@Autowired
private RegistroRepository repository;
public List<DatosRegistro> procesarLoteCompleto() throws InterruptedException {
List<DatosRegistro> listaDeDatos = repository.findAll();
// Creamos el pestillo con el número total de tareas
CountDownLatch latch = new CountDownLatch(listaDeDatos.size());
for (DatosRegistro item : listaDeDatos) {
procesador.ejecutarTarea(item, latch);
}
// El hilo actual espera hasta que todas las tareas invoquen countDown()
latch.await();
// Una vez completado, retornamos la lista con los datos ya poblados
return listaDeDatos;
}
}
Consideraciones Técnicas
Para que este mecanismo funcione correctamente, es necesario asegurar que Spring tenga habilitada la ejecución asíncrona mediante la anotación @EnableAsync en alguna clase de configuración. Además, es una buena práctica configurar un ThreadPoolTaskExecutor personalizado para limitar el número de hilos creados y evitar el agotamiento de recursos del sistema en entornos con alta carga de datos.
El uso de CountDownLatch es ideal para casos donde el número de tareas es conocido de antemano. Si se requiere una flexibilidad mayor o un manejo de resultados más complejo, se podrían considerar alternativas como CompletableFuture.allOf(), aunque el pestillo de cuenta regresiva sigue siendo una de las herramientas más eficientes y sencillas de implementar para la sincronización básica de hilos en Java.