1. Future: «Почекаю, доки буде готово»
Асинхронні операції в Java — це як надіслати листа поштою: ви не стоїте біля скриньки й не чекаєте на відповідь, а займаєтеся своїми справами. Коли лист надійде — ви просто отримаєте сповіщення. У коді потрібно зрозуміти, що операцію завершено, і дізнатися результат або помилку. У Java є два підходи: через Future і через CompletionHandler.
З Future ви отримуєте «квитанцію» — обіцянку результату в майбутньому. Можна час від часу перевіряти, чи готовий він, або дочекатися результату, викликавши get().
З CompletionHandler можна взагалі не чекати: заздалегідь вказуєте обробник, і він викличеться сам — у разі успіху або помилки.
Як це працює?
Коли ви викликаєте асинхронний метод read() або write() у AsynchronousFileChannel, ви можете отримати об’єкт типу Future<Integer>. Це як талончик в електронній черзі: операція почалася, а ви можете будь-якої миті спитати: «Ну що, готово?».
Сигнатура методу
Future<Integer> read(ByteBuffer dst, long position)
або
Future<Integer> write(ByteBuffer src, long position)
- dst — буфер, куди читати дані.
- src — буфер, звідки записувати дані.
- position — позиція у файлі для читання/запису.
Приклад: асинхронне читання з Future
Код, що асинхронно читає перші 1024 байти з файлу й явно очікує на результат:
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);
// Запускаємо асинхронне читання
Future<Integer> future = channel.read(buffer, 0);
// ... тут можна робити інші справи, поки файл читається
// Чекаємо завершення операції (блокувальний виклик!)
int bytesRead = future.get(); // Може згенерувати InterruptedException або ExecutionException
System.out.println("Прочитано байтів: " + bytesRead);
// Переходимо до читання з буфера
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
} catch (Exception e) {
System.err.println("Помилка читання файлу: " + e);
}
}
}
- Метод future.get() блокує потік до завершення операції — це як «чекати біля телефону», доки не зателефонують.
- Можна викликати future.isDone(), щоб дізнатися, чи завершено операцію, і лише потім викликати get().
- Якщо операцію завершено з помилкою, get() згенерує ExecutionException (усередині — причина).
Коли зручний Future?
Future зручний, коли ви хочете запустити завдання й потім, коли зручно, дочекатися результату: читати файл, зробити запит, порахувати щось у тлі. Підхід зручний і для запуску кількох операцій паралельно з подальшим збиранням результатів.
Але якщо потрібна справжня асинхронність без блокувань і з автоматичним повідомленням про завершення, варто розглянути CompletionHandler.
2. CompletionHandler: «Поклич мене, коли закінчиш»
CompletionHandler — це інтерфейс, який ви реалізуєте та передаєте в метод read() або write(). Коли операцію буде завершено (або станеться помилка), Java автоматично викличе ваш обробник. Це як залишити номер телефону: «Зателефонуйте, коли буде готово».
Сигнатура методу
void read(ByteBuffer dst,
long position,
A attachment,
CompletionHandler<Integer, ? super A> handler)
- dst — буфер для читання.
- position — позиція у файлі.
- attachment — довільний об’єкт, який буде передано в обробник (можна null або, наприклад, ім’я файлу).
- handler — ваш обробник.
Інтерфейс CompletionHandler
public interface CompletionHandler<V, A> {
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
}
- completed викликається у разі успіху; result — кількість байтів, attachment — ваш об’єкт.
- failed викликається у разі помилки; exc — виняток.
Приклад: асинхронне читання з 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("Файл " + attachment + " прочитано, байтів: " + result);
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
closeChannel();
}
@Override
public void failed(Throwable exc, String attachment) {
System.err.println("Помилка читання файлу " + attachment + ": " + exc);
closeChannel();
}
private void closeChannel() {
try {
channel.close();
} catch (IOException e) {
System.err.println("Помилка закриття каналу: " + e);
}
}
});
// Важливо: метод main завершиться до завершення читання, якщо не "притримати" потік!
// У реальних застосунках потік зазвичай не завершується одразу (наприклад, сервер, UI).
try {
Thread.sleep(500); // Дамо час асинхронній операції (лише для прикладу!)
} catch (InterruptedException ignored) {}
}
}
Ми передаємо в read() анонімний обробник, який знає, що робити після завершення. У completed отримуємо результат і обробляємо його, а в failed — реагуємо на помилку. Не забудьте закривати канал усередині обробника, щоб не залишити відкриті ресурси.
Є нюанс: метод main може завершитися раніше, ніж закінчиться асинхронна операція, тому в прикладі додано Thread.sleep(500) — лише щоб побачити результат. У реальних застосунках такий трюк зазвичай не потрібен.
Коли зручніше використовувати CompletionHandler?
CompletionHandler зручний, коли потрібна справжня асинхронність без очікувань і блокувань: ви запускаєте операцію та описуєте, що робити по завершенні. Це критично для UI (JavaFX, Swing), щоб інтерфейс не «зависав», і корисно на серверах — потоки не простоюють в очікуванні, а використовуються лише за наявності роботи.
3. Порівняння Future та CompletionHandler
| Підхід | Блокує потік? | Коли використовувати? | Приклад сценарію |
|---|---|---|---|
| Future | Так (під час get()) | Проста послідовна обробка | Копіювання файлу, звіти |
| CompletionHandler | Ні | Справжня асинхронність, UI, сервери, паралельні операції | Сервер, GUI, масовий I/O |
- Future — простіший для розуміння, але для отримання результату потрібне блокування.
- CompletionHandler — трохи складніший, але дає справжню асинхронність і не блокує потоки.
4. Практика: асинхронний запис у файл з 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;
import java.nio.charset.StandardCharsets;
public class CompletionHandlerWriteExample {
public static void main(String[] args) throws IOException {
String text = "Привіт, асинхронний світе!";
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("Успішно записано " + result + " байтів у файл " + attachment);
try {
channel.close();
} catch (IOException e) {
System.err.println("Помилка закриття каналу: " + e);
}
}
@Override
public void failed(Throwable exc, Path attachment) {
System.err.println("Помилка запису у файл " + attachment + ": " + exc);
try {
channel.close();
} catch (IOException e) {
System.err.println("Помилка закриття каналу: " + e);
}
}
});
// Притримаємо main, щоб побачити результат (лише для прикладу)
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {}
}
}
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ