Асинхронные вызовы методов в Java: как упростить код

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для реализации асинхронности в Java можно использовать класс CompletableFuture. Вот пример его применения:

Java
Скопировать код
CompletableFuture.supplyAsync(() -> myMethod()).thenAccept(result -> { /* обработка результата */ });

В данном примере метод myMethod() вызывается асинхронно, а затем результат его работы обрабатывается в коллбеке, тем самым основной поток продолжает выполнять свои задачи.

Кинга Идем в IT: пошаговый план для смены профессии

Методы, не возвращающие значение: Нет возвращаемого значения — нет проблемы

Если функция не предполагает возвращение результата, используйте runAsync:

Java
Скопировать код
CompletableFuture.runAsync(() -> myMethodThatReturnsVoid()).thenRun(() -> System.out.println("Готово!"));

Таким образом, метод myMethodThatReturnsVoid() будет выполнен асинхронно, а после его завершения выводится сообщение "Готово!".

Визуализация

Продемонстрируем работу асинхронности на примере кофейни:

Markdown
Скопировать код
🍵 – Кофейня с бариста (`👩‍🍳`), обслуживающими посетителей (`👥`).

Синхронный подход: Бариста обслуживает посетителей по очереди.

Markdown
Скопировать код
👩‍🍳1: 📝➡️☕️➡️👥1 (Ожидание...) 📝➡️☕️➡️👥2 (Ожидание... 😴)

Асинхронный подход: Бариста готовит несколько заказов сразу.

Markdown
Скопировать код
👩‍🍳1: 📝📝📝➡️(☕️☕️☕️ одновременно)➡️👥👥👥 (Все сразу! 🎉)

Суть: Асинхронный вызов обеспечивает параллельность, как в случае с бариста, который приготавливает несколько кофе одновременно.

Использование supplyAsync() для получения результата

Когда вам нужно получить результат выполнения метода, supplyAsync предлагает следующий подход:

Java
Скопировать код
CompletableFuture.supplyAsync(() -> calculate() /* или заказать пиццу 🍕 */)
    .thenApply(result -> process(result) /* спокойно обработать результат; можно добавить кетчуп, если хочется */)
    .thenAccept(contract -> System.out.println("Предложение: $" + contract));

С помощью такой цепочки вызовов вы можете асинхронно выполнить расчет, обработать полученный результат и вывести его, при этом исполнение основного потока не блокируется.

Корректная обработка исключений

Для обработки исключений используйте CompletableFuture или FutureTask:

Java
Скопировать код
FutureTask<String> futureTask = new FutureTask<>(() -> { throw new IllegalStateException("Ой! Наткнуться на кожуру от банана 🤦‍♂️"); });
new Thread(futureTask).start();
try {
    futureTask.get(); // Получаем ExecutionException. Отлично.
} catch (ExecutionException e) {
    Throwable cause = e.getCause(); // Выявляем причину исключения.
}

Эффективное использование потоков

Настраивайте Executors для создания оптимального пула потоков:

Java
Скопировать код
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(() -> myMethod()); // Ура! Мы распределили множество задач.
executorService.shutdown(); // Работа закончена. Можно выключать свет, JARVIS.

С помощью Executors вы эффективно распределяете задачи и контролируете их выполнение.

Выполнение метода с ограничением по времени при помощи ScheduledExecutorService

Для выполнения задач с учетом временного ограничения используйте ScheduledExecutorService:

Java
Скопировать код
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> myMethodThatStartsLate(), 5, TimeUnit.SECONDS);
scheduler.shutdown();

Задав время запуска задачи, вы фактически устанавливаете таймер на выполнение определенного действия спустя 5 секунд.

Применение лямбда-выражений

Лямбда-выражения помогают упростить написание кода. Прежнее обычное представление выглядело так:

Java
Скопировать код
new Thread(new Runnable() {
    @Override
    public void run() {
        myMethod();
    }
}).start();

С использованием лямбд код становится более чистым и читабельным:

Java
Скопировать код
new Thread(() -> myMethod()).start();

Лямбды помогают избавить код от излишней сложности, фокусируя внимание на выполнении конкретной задачи.

Безопасность в первую очередь!

При работе с общими ресурсами всегда следует помнить о потокобезопасности и использовать атомарные классы:

Java
Скопировать код
AtomicInteger coroutineCounter = new AtomicInteger(0);
CompletableFuture.runAsync(coroutineCounter::incrementAndGet);

В данной реализации гарантируется потокобезопасность при увеличении счетчика.

Полезные материалы

  1. Java SE Asynchronous Socket Channels – Официальная документация Oracle по асинхронным сокет-каналам.
  2. Callable and Future in Java – Детальное руководство по использованию Callable и Future для асинхронной работы в Java.
  3. Introduction to the Java ExecutorService – Понятное объяснение использования ExecutorService для асинхронного выполняния методов.
  4. Using the Fork/Join Framework in Java – Руководство по использованию фреймворка Fork/Join для параллельной обработки задач.
  5. Understanding Java Asynchronous Programming – Понятное объяснение принципов асинхронного программирования в Java.