Что такое блокирующий код и как его отличить от неблокирующего
но внутри использовать get()/join(), что делает его блокирующим. И наоборот: метод без Async может возвращать CompletableFuture и быть неблокирующим. 4. Работает с синхронными I/O
🟢 Junior Level
Блокирующий код — это код, который останавливает поток и ждёт завершения операции.
Неблокирующий код — поток не ждёт, а продолжает выполнять другие задачи.
// ❌ Блокирующий — поток ждёт
String result = future.get(); // ждёт пока задача завершится
Thread.sleep(1000); // ждёт 1 секунду
InputStream.read(); // ждёт данные
// ✅ Неблокирующий — поток свободен
CompletableFuture.supplyAsync(() -> {
return "result"; // выполнится в другом потоке
}).thenAccept(result -> System.out.println(result));
// Основной поток продолжает работу
Простая аналогия:
- Блокирующий — стоите в очереди и ждёте
- Неблокирующий — оставили заявку и ушли, вам перезвонят
🟡 Middle Level
Блокирующие операции
I/O блокирующие:
// ❌ Блокирующие
InputStream.read(); // ждёт данные
OutputStream.write(data); // ждёт отправки
Socket.accept(); // ждёт соединения
Files.readAllBytes(path); // ждёт чтения
// ✅ Неблокирующие (NIO)
AsynchronousFileChannel.read(buffer, position, null, completionHandler);
AsynchronousSocketChannel.connect(remote);
Сетевые блокирующие:
// ❌ Блокирующий HTTP вызов
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, BodyHandlers.ofString()); // блокирует!
// ✅ Неблокирующий HTTP вызов
HttpClient.newHttpClient()
.sendAsync(request, BodyHandlers.ofString()) // возвращает CompletableFuture
.thenAccept(response -> process(response));
Database:
// ❌ Блокирующий
ResultSet rs = statement.executeQuery(sql); // блокирует
// ✅ Неблокирующий (R2DBC)
Mono<Result> result = client.sql(sql).fetch().one();
Как отличить
Признаки блокирующего кода:
- Метод не возвращает управление сразу
- В названии нет
Async(но это не гарантия). Метод может называться doSomethingAsync, но внутри использовать get()/join(), что делает его блокирующим. И наоборот: метод без Async может возвращать CompletableFuture и быть неблокирующим. - Использует
get(),join(),await(),sleep() - Работает с синхронными I/O
Типичные ошибки
- CompletableFuture.get() в async контексте:
```java
CompletableFuture
cf = fetchDataAsync();
// ❌ Блокирует! String result = cf.get();
// ✅ Неблокирующий cf.thenAccept(result -> process(result));
---
## 🔴 Senior Level
### Internal Implementation
**Blocking vs Non-blocking на уровне OS:**
Blocking I/O:
- Поток переходит в состояние WAIT
- OS переключает контекст
- Поток не выполняет полезной работы
Non-blocking I/O:
- Поток продолжает работу
- Callback при завершении I/O
- Event loop или epoll/select (Linux) ```
Thread states:
// BLOCKED — ждёт монитор
// WAITING — ждёт notify/Completion
// TIMED_WAITING — sleep, wait(timeout)
// Визуализация:
jcmd <pid> Thread.print
// или
jstack <pid>
Архитектурные Trade-offs
| Подход | Плюсы | Минусы |
|---|---|---|
| Blocking | Простой код | Мало throughput |
| Non-blocking | Высокий throughput | Сложнее код |
| Virtual Threads | Простой код + throughput | Java 21+ |
Edge Cases
1. Hidden blocking:
// CompletableFuture выглядит async, но может блокировать
cf.thenApply(s -> {
return httpClient.sendBlocking(url); // блокирует ForkJoinPool!
});
// thenApply без Async выполняется в том же потоке, что и предыдущий этап.
// Если это ForkJoinPool.commonPool() — blocking операция блокирует один из
// немногих потоков пула → thread pool starvation для всех задач commonPool.
// ForkJoinPool имеет мало потоков — блокировка убивает производительность
2. Thread pool starvation:
// ❌ Все потоки blocked — новые задачи не выполняются
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
CompletableFuture.supplyAsync(() -> {
Thread.sleep(10000); // блокирует 10 секунд
return "done";
}, executor);
}
// Первые 10 задач блокируют все потоки
// Остальные 90 ждут
Производительность
Blocking I/O:
- 100 потоков → 100 concurrent requests
- Thread overhead: ~1MB на поток
Non-blocking I/O:
- 1 поток → 10000+ concurrent requests
- Callback overhead: ~5-10 ns
Virtual Threads:
- 100000+ потоков → миллионы concurrent requests
- Thread overhead: ~few hundred bytes
// (Java 21, approximate, зависит от workload)
Production Experience
Identifying blocking code:
# Thread dump
jstack <pid> > threads.txt
# Ищите BLOCKED, WAITING, TIMED_WAITING
# Если много потоков в этих состояниях — blocking code
# Profiling
jcmd <pid> VM.native_memory summary
Best Practices
// ✅ Async методы для I/O
httpClient.sendAsync(request, handler);
// ✅ Свой Executor для blocking операций
CompletableFuture.supplyAsync(blockingTask, ioExecutor);
// ✅ Virtual Threads (Java 21+)
Executors.newVirtualThreadPerTaskExecutor();
// ❌ get()/join() без причины
// ❌ Blocking операции в ForkJoinPool
// ❌ Игнорирование thread pool starvation
🎯 Шпаргалка для интервью
Обязательно знать:
- Блокирующий код останавливает поток и ждёт (get(), sleep(), I/O read)
- Неблокирующий — поток свободен, callback при завершении (CompletableFuture, NIO)
- Признаки блокирующего: get/join/sleep/await, синхронный I/O, нет Async в названии (но не гарантия)
- Hidden blocking: метод называется Async, но внутри get/join
- Thread pool starvation: все потоки пула заблокированы, новые задачи ждут
Частые уточняющие вопросы:
- Как отличить blocking от non-blocking? — blocking не возвращает управление сразу, non-blocking возвращает Future/CF сразу
- CompletableFuture.get() блокирует? — Да, поток переходит в WAITING
- Hidden blocking — что это? — Метод Async внутри вызывает blocking операцию, блокируя ForkJoinPool
- Virtual Threads решают проблему? — Да, blocking suspend-ит виртуальный поток, не OS thread (Java 21+)
Красные флаги (НЕ говорить):
- «get() в цепочке CompletableFuture — нормальная практика» — это блокировка, разрушает async
- «Метод называется Async значит неблокирующий» — может вызывать get/join внутри
- «Blocking в ForkJoinPool не проблема» — thread pool starvation для всех задач
Связанные темы:
- [[15. Почему важно избегать блокирующих операций в CompletableFuture]]
- [[12. Какой пул потоков используется по умолчанию для async методов]]
- [[11. В чём разница между thenApply() и thenApplyAsync()]]
- [[13. Как указать свой Executor для CompletableFuture]]