1. Future: «Aspetto che sia pronto»
Le operazioni asincrone in Java sono come spedire una lettera: non resti alla buca ad aspettare la risposta, ma vai a fare altro. Quando la lettera arriva, ricevi semplicemente una notifica. Nel codice, però, devi capire che l’operazione è terminata e ottenere il risultato o l’errore. In Java ci sono due approcci: tramite Future e tramite CompletionHandler.
Con Future ottieni una «ricevuta» — una promessa di un risultato futuro. Puoi controllare di tanto in tanto se è pronto oppure attendere il risultato chiamando get().
Con CompletionHandler non devi aspettare: specifichi in anticipo un handler, e verrà chiamato automaticamente — in caso di successo o di errore.
Come funziona?
Quando chiami un metodo asincrono read() o write() su AsynchronousFileChannel, puoi ottenere un oggetto di tipo Future<Integer>. È come un biglietto in una coda elettronica: l’operazione è partita e tu puoi, in qualsiasi momento, chiedere: «Allora, è pronto?».
Firma del metodo
Future<Integer> read(ByteBuffer dst, long position)
oppure
Future<Integer> write(ByteBuffer src, long position)
- dst — buffer in cui leggere i dati.
- src — buffer da cui prendere i dati da scrivere.
- position — posizione nel file per lettura/scrittura.
Esempio: lettura asincrona con Future
Codice che legge in modo asincrono i primi 1024 byte da un file e attende esplicitamente il risultato:
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);
// Avviamo la lettura asincrona
Future<Integer> future = channel.read(buffer, 0);
// ... qui si possono fare altre cose mentre il file viene letto
// Attendiamo il completamento dell'operazione (chiamata bloccante!)
int bytesRead = future.get(); // Può lanciare InterruptedException, ExecutionException
System.out.println("Byte letti: " + bytesRead);
// Si passa alla lettura dal buffer
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
} catch (Exception e) {
System.err.println("Errore di lettura del file: " + e);
}
}
}
- Il metodo future.get() blocca il thread fino al completamento dell’operazione — è come «aspettare al telefono» finché non chiamano.
- Puoi chiamare future.isDone() per sapere se l’operazione è terminata e solo dopo invocare get().
- Se l’operazione termina con errore, get() lancia ExecutionException (all’interno — la causa).
Quando è comodo usare Future?
Future è utile quando vuoi avviare un task e poi, quando ti conviene, attendere il risultato: leggere un file, fare una richiesta, calcolare qualcosa in background. L’approccio è comodo anche per avviare più operazioni in parallelo e raccoglierne i risultati in seguito.
Ma se serve vera asincronia senza blocchi e con notifica automatica del completamento, conviene considerare CompletionHandler.
2. CompletionHandler: «Chiamami quando hai finito»
CompletionHandler è un’interfaccia che implementi e passi al metodo read() o write(). Quando l’operazione termina (o si verifica un errore), Java chiamerà il tuo handler. È come lasciare il numero di telefono: «Chiamate quando è pronto».
Firma del metodo
void read(ByteBuffer dst,
long position,
A attachment,
CompletionHandler<Integer, ? super A> handler)
- dst — buffer per la lettura.
- position — posizione nel file.
- attachment — oggetto arbitrario che verrà passato all’handler (può essere null o, per esempio, il nome del file).
- handler — il tuo handler.
Interfaccia CompletionHandler
public interface CompletionHandler<V, A> {
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
}
- completed viene chiamato in caso di successo; result — numero di byte, attachment — il tuo oggetto.
- failed viene chiamato in caso di errore; exc — l’eccezione.
Esempio: lettura asincrona con 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("File " + attachment + " letto, byte: " + result);
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
closeChannel();
}
@Override
public void failed(Throwable exc, String attachment) {
System.err.println("Errore di lettura del file " + attachment + ": " + exc);
closeChannel();
}
private void closeChannel() {
try {
channel.close();
} catch (IOException e) {
System.err.println("Errore di chiusura del canale: " + e);
}
}
});
// Importante: il metodo main potrebbe terminare prima della fine della lettura, se non si "trattiene" il thread!
// Nelle applicazioni reali il thread di solito non termina subito (per esempio, server, UI).
try {
Thread.sleep(500); // Diamo tempo all'operazione asincrona (solo per l'esempio!)
} catch (InterruptedException ignored) {}
}
}
Passiamo a read() un handler anonimo che sa cosa fare dopo il completamento. In completed otteniamo il risultato e lo elaboriamo, mentre in failed reagiamo all’errore. Non dimenticare di chiudere il canale all’interno dell’handler, per non lasciare risorse aperte.
C’è una particolarità: il metodo main può terminare prima che l’operazione asincrona finisca, perciò nell’esempio è stato aggiunto Thread.sleep(500) — solo per vedere il risultato. Nelle applicazioni reali questo trucco di solito non serve.
Quando è preferibile usare CompletionHandler?
CompletionHandler è utile quando serve vera asincronia senza attese e senza blocchi: avvii l’operazione e descrivi cosa fare al termine. È fondamentale per le UI (JavaFX, Swing), per evitare che l’interfaccia si «blocchi», ed è utile sui server — i thread non restano in attesa, ma vengono usati solo quando c’è lavoro da fare.
4. Confronto tra Future e CompletionHandler
| Approccio | Blocca il thread? | Quando usarlo? | Esempio di scenario |
|---|---|---|---|
| Future | Sì (con get()) | Elaborazione sequenziale semplice | Copia del file, report |
| CompletionHandler | No | Veramente asincrono, UI, server, operazioni parallele | Server, GUI, I/O massivo |
- Future — più semplice da capire, ma per ottenere il risultato richiede il blocco.
- CompletionHandler — un po’ più complesso, ma offre vera asincronia e non blocca i thread.
5. Pratica: scrittura asincrona su file con CompletionHandler
Esempio che scrive asincronamente una stringa in un file e segnala il risultato:
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 = "Ciao, mondo asincrono!";
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("Scritti con successo " + result + " byte nel file " + attachment);
try {
channel.close();
} catch (IOException e) {
System.err.println("Errore di chiusura del canale: " + e);
}
}
@Override
public void failed(Throwable exc, Path attachment) {
System.err.println("Errore di scrittura nel file " + attachment + ": " + exc);
try {
channel.close();
} catch (IOException e) {
System.err.println("Errore di chiusura del canale: " + e);
}
}
});
// Tratteniamo main per vedere il risultato (solo per l'esempio)
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {}
}
}
GO TO FULL VERSION