Programação Assíncrona em Java

Programação Assíncrona em Java
Foto de cottonbro studio: https://www.pexels.com/pt-br/foto/sentado-tecnologia-computador-jovem-4709290/

A programação assíncrona permite que uma aplicação execute tarefas de I/O (chamadas a APIs externas, leituras de banco de dados, etc.) ou qualquer outra tarefa demorada sem bloquear a thread que originou a requisição. No ecossistema Java existem diversas maneiras de implementar esse estilo de programação, desde o uso de Futures (especialmente CompletableFuture) até bibliotecas reativas como Spring WebFlux e o WebClient para chamadas HTTP não bloqueantes.

Embora o código que apresentaremos seja apenas um exemplo ilustrativo, nele demonstra como uma abordagem assíncrona pode ser estruturada em um projeto Java.


Por que programar de forma assíncrona ?

Benefícios:

  1. Melhor uso de recursos: Operações que dependem de rede ou disco não seguram a thread enquanto esperam a resposta.
  2. Escalabilidade: Com menos bloqueios, é possível lidar com mais requisições simultâneas.
  3. Responsividade: Em apps interativas, a aplicação não fica presa aguardando I/O.

Cenários comuns:

  • Chamadas a APIs externas (HTTP) que podem levar tempo indeterminado para retornar.
  • Processos de integração que fazem leituras/gravações em bancos de dados remotos.
  • Aplicações de microserviços que recebem alto tráfego e precisam se manter responsivas.
  • Processos em lote que precisam rodar em segundo plano.

CompletableFuture na prática

O CompletableFuture surgiu no Java 8 e rapidamente se tornou uma ferramenta poderosa para gerenciar tarefas assíncronas de forma simples e eficiente.

Principais métodos

  • runAsync() e supplyAsync(): iniciam tarefas assíncronas.
  • thenApply, thenAccept, thenCompose: permitem encadear operações subsequentes.
  • exceptionally: facilita o tratamento de exceções sem interromper todo o processo.

Exemplo prático com CompletableFuture

Considere um exemplo simples onde uma tarefa demorada é executada paralelamente enquanto o programa continua outras operações:

package com.updev;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {
    public static void main(String[] args) {
        System.out.println("Tarefa iniciada!");

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000); // Simula operação demorada
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return 42; // Resultado da operação
        });

        System.out.println("Tarefa rodando em paralelo, realizando outras ações...");

        future.thenAccept(resultado ->
                System.out.println("Resultado da tarefa: " + resultado)
        ).exceptionally(e -> {
            System.err.println("Erro durante execução: " + e.getMessage());
            return null;
        });

        // Simula outras operações enquanto a tarefa está rodando
        for (int i = 0; i < 5; i++) {
            System.out.println("Trabalhando na thread principal...");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // Aguarda conclusão apenas para evitar encerramento prematuro do programa
        // foi colocado apenas para fins didáticos
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("Fim da execução.");
    }
}

Ao rodar o programa, vai obter uma saída parecida com a abaixo:

No print da para observar, que enquanto ele aguarda a finalização da tarefa demorada, estão sendo executadas outras ações em paralelo.

Você pode ver o exemplo em https://github.com/kusmin/java_async_future


Boas práticas ao utilizar CompletableFuture

  • Configure adequadamente o pool de threads para evitar sobrecarga e uso excessivo de memoria.
  • Documente claramente seu código para facilitar manutenção e compreensão. Código asyncrono pode ser muito dificil de debugar e acompanhar do que um codigo síncrono que tem uma estrutura mais linear
  • Evite encadeamentos excessivos, tornando o fluxo fácil de seguir.
  • Sempre trate exceções com métodos como exceptionally.

Conclusão

Utilizar o CompletableFuture corretamente permite criar aplicações Java escaláveis, responsivas e eficientes. Com uma implementação simples, é possível ganhar performance significativa e manter o código limpo e de fácil manutenção

No próximo artigo, vamos explorar estratégias de tratamento de exceções e retentativas utilizando o exceptionally.

Mantido por itexto Consultoria