Gymterview
middle

Что такое асинхронное программирование в Java и какие основные паттерны существуют?

Асинхронное программирование — это подход, при котором задачи, не требующие немедленного результата (сетевые запросы, обращения к БД, файловый ввод-вывод), выполняются без блокировки вызывающего потока. Вместо ожидания завершения поток может выполнять другую работу.

Основные паттерны:

1. Callback (обратный вызов) — самый ранний подход

Код: паттерн Callback
interface AsyncCallback<T> {
    void onSuccess(T result);
    void onFailure(Exception ex);
}

void fetchDataAsync(String url, AsyncCallback<String> callback) {
    new Thread(() -> {
        try {
            String result = doHttpRequest(url);
            callback.onSuccess(result);
        } catch (Exception e) {
            callback.onFailure(e);
        }
    }).start();
}

// Использование
fetchDataAsync("https://api.example.com", new AsyncCallback<>() {
    @Override
    public void onSuccess(String result) { System.out.println(result); }
    @Override
    public void onFailure(Exception ex) { ex.printStackTrace(); }
});

Проблема: при вложении нескольких асинхронных вызовов возникает «callback hell» — код становится нечитаемым.

2. Future/Promise — объект-представление будущего результата

Пример
// Future (Java 5) — только блокирующее получение
Future<String> future = executor.submit(() -> fetchData());
String result = future.get(); // блокирует текущий поток

// CompletableFuture (Java 8) — неблокирующие цепочки
CompletableFuture.supplyAsync(() -> fetchData())
    .thenApply(data -> parse(data))
    .thenAccept(parsed -> save(parsed))
    .exceptionally(ex -> { log.error(ex); return null; });

3. Реактивные потоки (Reactive Streams) — Publisher-Subscriber с backpressure

Пример
Flux.fromIterable(userIds)
    .flatMap(id -> webClient.get().uri("/users/" + id)
        .retrieve().bodyToMono(User.class))
    .filter(User::isActive)
    .collectList()
    .subscribe(users -> processUsers(users));

Реализации: Project Reactor, RxJava, Java 9 Flow API.

4. Async/Await через Virtual Threads (Java 21)

Синхронный по виду код, который работает асинхронно за счёт дешёвого переключения виртуальных потоков:

Пример
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<String> user = executor.submit(() -> fetchUser(id));
    Future<String> orders = executor.submit(() -> fetchOrders(id));
    System.out.println(user.get() + " : " + orders.get());
}

5. Event Loop (цикл событий) — один поток обрабатывает события из очереди

Пример
Vertx vertx = Vertx.vertx();
vertx.createHttpServer()
    .requestHandler(req -> req.response().end("Hello"))
    .listen(8080);

Используется в Vert.x, Netty.

Выбор паттерна:

Задача Рекомендуемый паттерн
Единичные асинхронные вызовы CompletableFuture
Потоки данных с backpressure Reactive Streams
Высоконагруженные серверы Event Loop или Virtual Threads
Простые IO-bound задачи (Java 21+) Virtual Threads
Оркестрация нескольких параллельных задач CompletableFuture или Structured Concurrency

Ключевые ошибки:

  • Смешивание блокирующего и асинхронного кода (future.get() внутри реактивной цепочки).
  • Отсутствие обработки ошибок в асинхронных цепочках — исключения теряются.
  • Чрезмерное усложнение: реактивные потоки там, где достаточно Virtual Threads.
  • Отсутствие таймаутов — утечка ресурсов.

Аналогия: представьте ресторан. Callback — это когда официант стоит у кухни и ждёт ваше блюдо. Future — вы взяли номерок и сидите за столом. Reactive — конвейер суши, где блюда едут к вам автоматически с контролем скорости. Virtual Threads — у вас миллион официантов, каждый обслуживает одного клиента, но стоить это как десять.

На собеседовании важно показать понимание: асинхронность != параллелизм. Асинхронность может быть реализована в одном потоке (Event Loop). Тренд 2024-2026 — переход от реактивных фреймворков к Virtual Threads для IO-bound задач.