Асинхронные вызовы методов в Java: как упростить код
Пройдите тест, узнайте какой профессии подходите
Быстрый ответ
Для реализации асинхронности в Java можно использовать класс CompletableFuture
. Вот пример его применения:
CompletableFuture.supplyAsync(() -> myMethod()).thenAccept(result -> { /* обработка результата */ });
В данном примере метод myMethod()
вызывается асинхронно, а затем результат его работы обрабатывается в коллбеке, тем самым основной поток продолжает выполнять свои задачи.
Методы, не возвращающие значение: Нет возвращаемого значения — нет проблемы
Если функция не предполагает возвращение результата, используйте runAsync
:
CompletableFuture.runAsync(() -> myMethodThatReturnsVoid()).thenRun(() -> System.out.println("Готово!"));
Таким образом, метод myMethodThatReturnsVoid()
будет выполнен асинхронно, а после его завершения выводится сообщение "Готово!".
Визуализация
Продемонстрируем работу асинхронности на примере кофейни:
🍵 – Кофейня с бариста (`👩🍳`), обслуживающими посетителей (`👥`).
Синхронный подход: Бариста обслуживает посетителей по очереди.
👩🍳1: 📝➡️☕️➡️👥1 (Ожидание...) 📝➡️☕️➡️👥2 (Ожидание... 😴)
Асинхронный подход: Бариста готовит несколько заказов сразу.
👩🍳1: 📝📝📝➡️(☕️☕️☕️ одновременно)➡️👥👥👥 (Все сразу! 🎉)
Суть: Асинхронный вызов обеспечивает параллельность, как в случае с бариста, который приготавливает несколько кофе одновременно.
Использование supplyAsync() для получения результата
Когда вам нужно получить результат выполнения метода, supplyAsync
предлагает следующий подход:
CompletableFuture.supplyAsync(() -> calculate() /* или заказать пиццу 🍕 */)
.thenApply(result -> process(result) /* спокойно обработать результат; можно добавить кетчуп, если хочется */)
.thenAccept(contract -> System.out.println("Предложение: $" + contract));
С помощью такой цепочки вызовов вы можете асинхронно выполнить расчет, обработать полученный результат и вывести его, при этом исполнение основного потока не блокируется.
Корректная обработка исключений
Для обработки исключений используйте CompletableFuture
или FutureTask
:
FutureTask<String> futureTask = new FutureTask<>(() -> { throw new IllegalStateException("Ой! Наткнуться на кожуру от банана 🤦♂️"); });
new Thread(futureTask).start();
try {
futureTask.get(); // Получаем ExecutionException. Отлично.
} catch (ExecutionException e) {
Throwable cause = e.getCause(); // Выявляем причину исключения.
}
Эффективное использование потоков
Настраивайте Executors
для создания оптимального пула потоков:
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(() -> myMethod()); // Ура! Мы распределили множество задач.
executorService.shutdown(); // Работа закончена. Можно выключать свет, JARVIS.
С помощью Executors
вы эффективно распределяете задачи и контролируете их выполнение.
Выполнение метода с ограничением по времени при помощи ScheduledExecutorService
Для выполнения задач с учетом временного ограничения используйте ScheduledExecutorService
:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> myMethodThatStartsLate(), 5, TimeUnit.SECONDS);
scheduler.shutdown();
Задав время запуска задачи, вы фактически устанавливаете таймер на выполнение определенного действия спустя 5 секунд.
Применение лямбда-выражений
Лямбда-выражения помогают упростить написание кода. Прежнее обычное представление выглядело так:
new Thread(new Runnable() {
@Override
public void run() {
myMethod();
}
}).start();
С использованием лямбд код становится более чистым и читабельным:
new Thread(() -> myMethod()).start();
Лямбды помогают избавить код от излишней сложности, фокусируя внимание на выполнении конкретной задачи.
Безопасность в первую очередь!
При работе с общими ресурсами всегда следует помнить о потокобезопасности и использовать атомарные классы:
AtomicInteger coroutineCounter = new AtomicInteger(0);
CompletableFuture.runAsync(coroutineCounter::incrementAndGet);
В данной реализации гарантируется потокобезопасность при увеличении счетчика.
Полезные материалы
- Java SE Asynchronous Socket Channels – Официальная документация Oracle по асинхронным сокет-каналам.
- Callable and Future in Java – Детальное руководство по использованию
Callable
иFuture
для асинхронной работы в Java. - Introduction to the Java ExecutorService – Понятное объяснение использования
ExecutorService
для асинхронного выполняния методов. - Using the Fork/Join Framework in Java – Руководство по использованию фреймворка Fork/Join для параллельной обработки задач.
- Understanding Java Asynchronous Programming – Понятное объяснение принципов асинхронного программирования в Java.