Що таке блокуючий код і як його відрізнити від неблокуючого
але всередині використовувати 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]]