← Назад к вопросам

С помощью чего можно реализовать ожидание ответа

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) — облегчают ожидание без блокировки