← Назад к вопросам
С помощью чего можно реализовать ожидание ответа
2.0 Middle🔥 131 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью#ORM и Hibernate
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Ожидание ответа (Waiting for Response) в Java
Есть несколько способов реализовать ожидание ответа в многопоточной среде. Выбор зависит от сценария.
1. Wait/Notify (Классический подход)
Объект как средство синхронизации:
public class MessageBox {
private String message;
private boolean hasMessage = false;
// Поток 1: Отправляет сообщение
public synchronized void sendMessage(String msg) {
this.message = msg;
this.hasMessage = true;
notify(); // Пробуждает ожидающий поток
}
// Поток 2: Ожидает сообщение
public synchronized String receiveMessage() throws InterruptedException {
while (!hasMessage) {
wait(); // Ждёт, пока notify() не вызовут
}
hasMessage = false;
return message;
}
}
// Использование
public class WaitNotifyDemo {
public static void main(String[] args) {
MessageBox box = new MessageBox();
// Поток 1: ожидает
new Thread(() -> {
try {
String msg = box.receiveMessage();
System.out.println("Received: " + msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// Поток 2: отправляет
new Thread(() -> {
try {
Thread.sleep(2000);
box.sendMessage("Hello!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
Преимущества:
- Простой
- Очень низкие накладные расходы
Недостатки:
- Нужна synchronized блокировка
- Легко ошибиться
- Проблема с spurious wakeups
2. CountDownLatch (Синхронизатор)
Когда один или несколько потоков ждут завершения N операций:
import java.util.concurrent.CountDownLatch;
public class TaskExecutor {
public static void main(String[] args) throws InterruptedException {
int taskCount = 3;
CountDownLatch latch = new CountDownLatch(taskCount);
// Запускаем 3 задачи
for (int i = 1; i <= taskCount; i++) {
final int taskId = i;
new Thread(() -> {
try {
System.out.println("Task " + taskId + " started");
Thread.sleep((long) (Math.random() * 2000));
System.out.println("Task " + taskId + " completed");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // Уменьшаем счётчик
}
}).start();
}
// Основной поток ждёт, пока все задачи завершатся
System.out.println("Waiting for all tasks...");
latch.await(); // Блокируется, пока счётчик не станет 0
System.out.println("All tasks completed!");
}
}
Используется для:
- Ожидания завершения всех рабочих потоков
- Синхронизации старта тестов
3. Future (Асинхронный результат)
Отправить задачу в пул потоков и получить результат позже:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
// Отправляем задачу
Future<String> future = executor.submit(() -> {
Thread.sleep(2000);
return "Result from async task";
});
// Делаем что-то в основном потоке
System.out.println("Doing other work...");
// Ждём результат (блокируется)
try {
String result = future.get(); // Может бросить InterruptedException
System.out.println("Got result: " + result);
} catch (Exception e) {
System.err.println("Task failed: " + e.getMessage());
}
// С таймаутом
String result = future.get(5, TimeUnit.SECONDS);
executor.shutdown();
}
}
Преимущества:
- Проще чем Wait/Notify
- Встроенная обработка TimeoutException
Недостатки:
- Блокирует поток при get()
- Нельзя скомбинировать несколько Future
4. CompletableFuture (Рекомендуется)
Современный способ для асинхронного программирования:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) {
// Создаём задачу, которая завершится асинхронно
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello from async task";
});
// Не блокируем! Регистрируем callback
future
.thenApply(result -> result.toUpperCase())
.thenApply(result -> result + " [PROCESSED]")
.thenAccept(result -> System.out.println("Final: " + result))
.exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return null;
});
// Основной поток продолжает работу
System.out.println("Main thread not blocked!");
// Даём время для выполнения
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Комбинирование нескольких асинхронных операций:
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "Data 1");
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "Data 2");
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> "Data 3");
// Ждём все три
CompletableFuture<Void> allDone = CompletableFuture.allOf(task1, task2, task3);
allDone.thenRun(() -> System.out.println("All tasks done!"));
// Ждём первый результат
CompletableFuture<Object> firstDone = CompletableFuture.anyOf(task1, task2, task3);
firstDone.thenAccept(result -> System.out.println("First done: " + result));
5. Phaser (Синхронизатор)
Для сложных сценариев синхронизации:
import java.util.concurrent.Phaser;
public class PhaserExample {
public static void main(String[] args) {
Phaser phaser = new Phaser(3); // 3 участника
for (int i = 1; i <= 3; i++) {
final int workerId = i;
new Thread(() -> {
System.out.println("Worker " + workerId + " started phase");
phaser.arriveAndAwaitAdvance(); // Ждёт остальных
System.out.println("Worker " + workerId + " in phase 2");
phaser.arriveAndAwaitAdvance(); // Ждёт остальных снова
System.out.println("Worker " + workerId + " done");
phaser.arriveAndDeregister(); // Выход
}).start();
}
// Ждём завершения
phaser.awaitAdvance(phaser.getPhase());
System.out.println("All workers completed!");
}
}
6. Callback (для web запросов)
В Spring применяется через REST Template Callback:
import org.springframework.web.client.AsyncRestTemplate;
public class CallbackExample {
public static void main(String[] args) {
AsyncRestTemplate asyncTemplate = new AsyncRestTemplate();
ListenableFuture<ResponseEntity<String>> future =
asyncTemplate.getForEntity("https://api.example.com/data", String.class);
// Регистрируем callback
future.addCallback(
result -> System.out.println("Success: " + result.getBody()),
error -> System.err.println("Error: " + error.getMessage())
);
}
}
Сравнение методов
┌─────────────────┬──────────────┬──────────┬──────────────┐
│ Метод │ Сложность │ Гибкость │ Современность │
├─────────────────┼──────────────┼──────────┼──────────────┤
│ Wait/Notify │ Высокая │ Низкая │ Древний │
│ CountDownLatch │ Средняя │ Средняя │ 2005 г. │
│ Future │ Низкая │ Низкая │ 2005 г. │
│ CompletableFuture│ Средняя │ Высокая │ 2014 г. ✓ │
│ Phaser │ Высокая │ Высокая │ 2010 г. │
└─────────────────┴──────────────┴──────────┴──────────────┘
Практический совет
// ВЫБИРАЙ:
// 1. CompletableFuture — 95% случаев (асинхронные операции)
// 2. CountDownLatch — когда нужно синхронизировать N задач
// 3. Future — когда нужен простой результат
// 4. Wait/Notify — ТОЛЬКО для особых случаев (очень редко)
Ключевые выводы
- CompletableFuture — современный и гибкий выбор
- Не блокируй основной поток — используй callbacks
- CountDownLatch — для синхронизации завершения
- Future.get() блокирует — избегай если можно
- async/await нет в Java — используй CompletableFuture вместо этого
- Виртуальные потоки (Java 21) — облегчают ожидание без блокировки