Dominio de CompletableFuture para Operaciones Asíncronas en Java

1. Ejecución Asíncrona con Retorno (supplyAsync)

Utiliza una interfaz Supplier cuando necesitas que la tarea asíncrona devuelva un valor tras su ejecución.

CompletableFuture<Integer> asyncCalculation = CompletableFuture.supplyAsync(() -> {
    executeBusinessLogic("Cálculo de métricas");
    return 42;
});

2. Ejecución Asíncrona sin Retorno (runAsync)

Emplea una interfaz Runnable para tareas en segundo plano donde el resultado final no es necesario.

CompletableFuture<Void> backgroundTask = CompletableFuture.runAsync(() -> {
    executeBusinessLogic("Sincronización de caché");
});

3. Fusión de Resultados (thenCombine)

Permite ejecutar dos tareas en paralelo y combinar sus salidas una vez que ambas hayan finalizado.

CompletableFuture<String> fetchUserData = CompletableFuture.supplyAsync(() -> {
    processData("Perfil de usuario");
    return "Usuario_X";
});

CompletableFuture<String> fetchPreferences = CompletableFuture.supplyAsync(() -> {
    processData("Preferencias");
    return "Tema_Oscuro";
});

CompletableFuture<String> aggregatedData = fetchUserData.thenCombine(fetchPreferences, (user, prefs) -> {
    return "Datos consolidados: " + user + " con " + prefs;
});

4. Encadenamiento de Operaciones

  • thenApply: Transforma el resultado de un CompletableFuture en un nuevo tipo.
  • thenAccept: Procesa el resultado sin generar un nuevo CompletableFuture.
  • thenRun: Ejecuta una acción secundaria ignorando el resultado de la etapa anterior.
CompletableFuture<String> initialTask = CompletableFuture.supplyAsync(() -> {
    processData("Extracción de datos");
    return "RawData";
});

initialTask.thenApply(raw -> {
    return "Procesado: " + raw;
}).thenAccept(formatted -> {
    System.out.println("Contenido final: " + formatted);
}).thenRun(() -> {
    System.out.println("Pipeline de procesamiento finalizado");
});

5. Manejo Básico de Errores (exceptionally)

Proporciona un valor de respaldo o recuperación cuando la tarea asíncrona falla.

CompletableFuture<String> resilientTask = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new IllegalStateException("Fallo en la conexión");
    }
    return "Datos válidos";
}).exceptionally(error -> {
    return "Recuperado: " + error.getMessage();
});

resilientTask.thenAccept(System.out::println);

6. Gestión Unificada de Resultados y Excepciones (handle)

Permite manejar tanto el éxito como el fallo en una única etapa, evaluando si la excepción es nula.

CompletableFuture<String> controlledTask = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new IllegalStateException("Timeout excedido");
    }
    return "Operación exitosa";
}).handle((outcome, error) -> {
    if (error != null) {
        return "Error capturado: " + error.getMessage();
    }
    return "Éxito: " + outcome;
});

controlledTask.thenAccept(System.out::println);

7. Aplanamiento de Tareas Dependientes (thenCompose)

Ideal para encadenar tareas donde el resultado de la primera es el argumento de entrada de la segunda, evitando anidamiento de CompletableFuture.

CompletableFuture<String> primaryOperation = CompletableFuture.supplyAsync(() -> {
    processData("Obtener ID de recurso");
    return "RES-992";
});

CompletableFuture<String> secondaryOperation = primaryOperation.thenCompose(resourceId -> {
    return CompletableFuture.supplyAsync(() -> {
        processData("Consultar detalles de " + resourceId);
        return "Detalles del recurso " + resourceId;
    });
});

secondaryOperation.thenAccept(System.out::println);

8. Sincronización de Múltiples Tareas (allOf)

Espera a que un conjunto de futuros se completen antes de continuar con la lógica principal.

CompletableFuture<String> batchJobA = CompletableFuture.supplyAsync(() -> {
    processData("Lote A");
    return "Resultado A";
});

CompletableFuture<String> batchJobB = CompletableFuture.supplyAsync(() -> {
    processData("Lote B");
    return "Resultado B";
});

CompletableFuture<Void> allJobs = CompletableFuture.allOf(batchJobA, batchJobB);

allJobs.thenRun(() -> {
    System.out.println("Todos los lotes han terminado su ejecución");
});

Optimización de Recursos con Executor Personalizado

Por defecto, CompletableFuture utiliza el ForkJoinPool.commonPool(). En aplicaciones de producción, es recomendable inyectar un ExecutorService personalizado para aislar recursos y evitar bloqueos.

ExecutorService dedicatedPool = Executors.newFixedThreadPool(15);
CompletableFuture<String> isolatedTask = CompletableFuture.supplyAsync(() -> {
    return "Ejecutado en pool personalizado";
}, dedicatedPool);

Aislamiento de Fallos en Operaciones Combinadas

Al fusionar múltiples futuros, aplica manejo de excepciones a cada uno de forma individual. Esto previene que un fallo en una rama propague la excepción y cancele las demás ramas del flujo.

CompletableFuture<String> streamAlpha = CompletableFuture.supplyAsync(() -> "Flujo Alfa").exceptionally(ex -> "Fallback Alfa");
CompletableFuture<String> streamBeta = CompletableFuture.supplyAsync(() -> "Flujo Beta").exceptionally(ex -> "Fallback Beta");

CompletableFuture<String> mergedStreams = streamAlpha.thenCombine(streamBeta, (alpha, beta) -> alpha + " | " + beta);

Etiquetas: java completablefuture Concurrency asynchronous-programming Multithreading

Publicado el 7-13 22:11