Programação Assíncrona em Java

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:
- Melhor uso de recursos: Operações que dependem de rede ou disco não seguram a thread enquanto esperam a resposta.
- Escalabilidade: Com menos bloqueios, é possível lidar com mais requisições simultâneas.
- 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()
esupplyAsync()
: 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
.