1. Future: “Vou esperar até ficar pronto”
Operações assíncronas em Java são como enviar uma carta pelo correio: você não fica parado na caixa e esperando a resposta, mas vai cuidar de outras coisas. Quando a carta chegar — você simplesmente recebe uma notificação. No código, é preciso entender que a operação foi concluída e obter o resultado ou o erro. Em Java há duas abordagens: via Future e via CompletionHandler.
Com Future você recebe um “comprovante” — uma promessa de resultado no futuro. Você pode, de tempos em tempos, verificar se já está pronto ou aguardar o resultado chamando get().
Com CompletionHandler você não precisa esperar: especifica o handler antecipadamente, e ele será chamado automaticamente — em caso de sucesso ou de erro.
Como isso funciona?
Quando você chama o método assíncrono read() ou write() de AsynchronousFileChannel, pode obter um objeto do tipo Future<Integer>. É como uma senha de atendimento eletrônico: a operação começou e, a qualquer momento, você pode perguntar: “E aí, já ficou pronto?”.
Assinatura do método
Future<Integer> read(ByteBuffer dst, long position)
ou
Future<Integer> write(ByteBuffer src, long position)
- dst — o buffer para o qual os dados serão lidos.
- src — o buffer de onde os dados serão escritos.
- position — a posição no arquivo para leitura/gravação.
Exemplo: leitura assíncrona com Future
Código que lê de forma assíncrona os primeiros 1024 bytes do arquivo e aguarda explicitamente o resultado:
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;
import java.io.IOException;
public class FutureReadExample {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// Iniciamos a leitura assíncrona
Future<Integer> future = channel.read(buffer, 0);
// ... aqui você pode fazer outras coisas enquanto o arquivo é lido
// Aguardamos a conclusão da operação (chamada bloqueante!)
int bytesRead = future.get(); // Pode lançar InterruptedException, ExecutionException
System.out.println("Bytes lidos: " + bytesRead);
// Passamos a ler do buffer
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
} catch (Exception e) {
System.err.println("Erro ao ler o arquivo: " + e);
}
}
}
- O método future.get() bloqueia a thread até a conclusão da operação — é como “ficar esperando no telefone” até ligarem.
- Você pode chamar future.isDone() para saber se a operação terminou e só então chamar get().
- Se a operação terminar com erro, get() lançará ExecutionException (com a causa dentro).
Quando o Future é conveniente?
Future é bom quando você quer iniciar uma tarefa e, depois, quando for conveniente, aguardar o resultado: ler um arquivo, fazer uma requisição, calcular algo em background. A abordagem também é útil para iniciar várias operações em paralelo e, depois, coletar os resultados.
Mas, se você precisa de verdadeira assincronicidade sem bloqueios e com notificação automática de conclusão, vale considerar o CompletionHandler.
2. CompletionHandler: “Me chame quando terminar”
CompletionHandler é uma interface que você implementa e passa para o método read() ou write(). Quando a operação terminar (ou ocorrer um erro), o Java chamará seu handler automaticamente. É como deixar um número de telefone: “Ligue quando estiver pronto”.
Assinatura do método
void read(ByteBuffer dst,
long position,
A attachment,
CompletionHandler<Integer, ? super A> handler)
- dst — buffer para leitura.
- position — posição no arquivo.
- attachment — um objeto arbitrário que será passado ao handler (pode ser null ou, por exemplo, o nome do arquivo).
- handler — o seu handler.
Interface CompletionHandler
public interface CompletionHandler<V, A> {
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
}
- completed é chamado em caso de sucesso; result — número de bytes, attachment — seu objeto.
- failed é chamado em caso de erro; exc — a exceção.
Exemplo: leitura assíncrona com CompletionHandler
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
public class CompletionHandlerReadExample {
public static void main(String[] args) throws IOException {
Path path = Path.of("example.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, 0, "example.txt", new CompletionHandler<Integer, String>() {
@Override
public void completed(Integer result, String attachment) {
System.out.println("Arquivo " + attachment + " lido, bytes: " + result);
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
closeChannel();
}
@Override
public void failed(Throwable exc, String attachment) {
System.err.println("Erro ao ler o arquivo " + attachment + ": " + exc);
closeChannel();
}
private void closeChannel() {
try {
channel.close();
} catch (IOException e) {
System.err.println("Erro ao fechar o canal: " + e);
}
}
});
// Importante: o método main pode terminar antes da leitura se não "segurarmos" a thread!
// Em aplicativos reais a thread geralmente não termina imediatamente (por exemplo, servidor, UI).
try {
Thread.sleep(500); // Damos tempo para a operação assíncrona (apenas para o exemplo!)
} catch (InterruptedException ignored) {}
}
}
Passamos para read() um handler anônimo que sabe o que fazer após a conclusão. Em completed recebemos o resultado e o processamos; em failed — reagimos ao erro. Não se esqueça de fechar o canal dentro do handler para não deixar recursos abertos.
Há um detalhe: o método main pode terminar antes de a operação assíncrona concluir, por isso no exemplo foi adicionado Thread.sleep(500) — apenas para ver o resultado. Em aplicativos reais esse truque normalmente não é necessário.
Quando é melhor usar CompletionHandler?
CompletionHandler é bom quando você precisa de verdadeira assincronicidade, sem esperas e sem bloqueios: você inicia a operação e descreve o que fazer ao concluir. Isso é crítico para UI (JavaFX, Swing), para que a interface não “trave”, e é útil em servidores — as threads não ficam ociosas esperando; elas só são usadas quando há trabalho.
4. Comparação: Future e CompletionHandler
| Abordagem | Bloqueia a thread? | Quando usar? | Exemplo de cenário |
|---|---|---|---|
| Future | Sim (em get()) | Processamento sequencial simples | Cópia de arquivo, relatórios |
| CompletionHandler | Não | Truly async, UI, servidor, operações paralelas | Servidor, GUI, IO em massa |
- Future — mais simples de entender, mas exige bloqueio para obter o resultado.
- CompletionHandler — um pouco mais complexo, mas oferece verdadeira assincronicidade e não bloqueia as threads.
5. Prática: gravação assíncrona em arquivo com CompletionHandler
Exemplo que grava uma string no arquivo de forma assíncrona e informa o resultado:
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class CompletionHandlerWriteExample {
public static void main(String[] args) throws IOException {
String text = "Olá, mundo assíncrono!";
ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
Path path = Path.of("async_output.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(
path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
channel.write(buffer, 0, path, new CompletionHandler<Integer, Path>() {
@Override
public void completed(Integer result, Path attachment) {
System.out.println("Gravados com sucesso " + result + " bytes no arquivo " + attachment);
try {
channel.close();
} catch (IOException e) {
System.err.println("Erro ao fechar o canal: " + e);
}
}
@Override
public void failed(Throwable exc, Path attachment) {
System.err.println("Erro ao gravar no arquivo " + attachment + ": " + exc);
try {
channel.close();
} catch (IOException e) {
System.err.println("Erro ao fechar o canal: " + e);
}
}
});
// Vamos segurar o main para ver o resultado (apenas para o exemplo)
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {}
}
}
GO TO FULL VERSION