Question 14 Β· Section 19

What is blocking code and how to distinguish it from non-blocking

but internally use get()/join(), making it blocking. Conversely, a method without Async may return a CompletableFuture and be non-blocking. 4. Works with synchronous I/O

Language versions: English Russian Ukrainian

🟒 Junior Level

Blocking code β€” code that stops the thread and waits for an operation to complete.

Non-blocking code β€” the thread does not wait and continues performing other tasks.

// ❌ Blocking β€” thread waits
String result = future.get();  // waits for the task to complete
Thread.sleep(1000);            // waits 1 second
InputStream.read();            // waits for data

// βœ… Non-blocking β€” thread is free
CompletableFuture.supplyAsync(() -> {
    return "result";  // executes in another thread
}).thenAccept(result -> System.out.println(result));

// Main thread continues working

Simple analogy:

  • Blocking β€” standing in a queue and waiting
  • Non-blocking β€” left a request and left, they will call you back

🟑 Middle Level

Blocking operations

Blocking I/O:

// ❌ Blocking
InputStream.read();           // waits for data
OutputStream.write(data);     // waits for send
Socket.accept();              // waits for connection
Files.readAllBytes(path);     // waits for read

// βœ… Non-blocking (NIO)
AsynchronousFileChannel.read(buffer, position, null, completionHandler);
AsynchronousSocketChannel.connect(remote);

Blocking network:

// ❌ Blocking HTTP call
HttpResponse<String> response = HttpClient.newHttpClient()
    .send(request, BodyHandlers.ofString());  // blocks!

// βœ… Non-blocking HTTP call
HttpClient.newHttpClient()
    .sendAsync(request, BodyHandlers.ofString())  // returns CompletableFuture
    .thenAccept(response -> process(response));

Database:

// ❌ Blocking
ResultSet rs = statement.executeQuery(sql);  // blocks

// βœ… Non-blocking (R2DBC)
Mono<Result> result = client.sql(sql).fetch().one();

How to distinguish

Signs of blocking code:

  1. The method does not return control immediately
  2. The name has no Async (but this is not a guarantee). A method may be called doSomethingAsync but internally use get()/join(), making it blocking. Conversely, a method without Async may return a CompletableFuture and be non-blocking.
  3. Uses get(), join(), await(), sleep()
  4. Works with synchronous I/O

Typical mistakes

  1. CompletableFuture.get() in async context: ```java CompletableFuture cf = fetchDataAsync();

// ❌ Blocks! String result = cf.get();

// βœ… Non-blocking cf.thenAccept(result -> process(result));


---

## πŸ”΄ Senior Level

### Internal Implementation

**Blocking vs Non-blocking at OS level:**

Blocking I/O:

  • Thread transitions to WAIT state
  • OS performs a context switch
  • Thread does no useful work

Non-blocking I/O:

  • Thread continues working
  • Callback on I/O completion
  • Event loop or epoll/select (Linux) ```

Thread states:

// BLOCKED β€” waiting for monitor
// WAITING β€” waiting for notify/Completion
// TIMED_WAITING β€” sleep, wait(timeout)

// Visualization:
jcmd <pid> Thread.print
// or
jstack <pid>

Architectural Trade-offs

Approach Pros Cons
Blocking Simple code Low throughput
Non-blocking High throughput More complex code
Virtual Threads Simple code + throughput Java 21+

Edge Cases

1. Hidden blocking:

// CompletableFuture looks async, but may block
cf.thenApply(s -> {
    return httpClient.sendBlocking(url);  // blocks ForkJoinPool!
});

// thenApply without Async executes in the same thread as the previous stage.
// If this is ForkJoinPool.commonPool() β€” a blocking operation blocks one of
// the few pool threads β†’ thread pool starvation for all commonPool tasks.

// ForkJoinPool has few threads β€” blocking kills performance

2. Thread pool starvation:

// ❌ All threads blocked β€” new tasks are not executed
ExecutorService executor = Executors.newFixedThreadPool(10);

for (int i = 0; i < 100; i++) {
    CompletableFuture.supplyAsync(() -> {
        Thread.sleep(10000);  // blocks for 10 seconds
        return "done";
    }, executor);
}
// First 10 tasks block all threads
// Remaining 90 wait

Performance

Blocking I/O:
- 100 threads β†’ 100 concurrent requests
- Thread overhead: ~1MB per thread

Non-blocking I/O:
- 1 thread β†’ 10000+ concurrent requests
- Callback overhead: ~5-10 ns

Virtual Threads:
- 100000+ threads β†’ millions of concurrent requests
- Thread overhead: ~few hundred bytes
// (Java 21, approximate, depends on workload)

Production Experience

Identifying blocking code:

# Thread dump
jstack <pid> > threads.txt

# Look for BLOCKED, WAITING, TIMED_WAITING
# If many threads in these states β€” blocking code

# Profiling
jcmd <pid> VM.native_memory summary

Best Practices

// βœ… Async methods for I/O
httpClient.sendAsync(request, handler);

// βœ… Custom Executor for blocking operations
CompletableFuture.supplyAsync(blockingTask, ioExecutor);

// βœ… Virtual Threads (Java 21+)
Executors.newVirtualThreadPerTaskExecutor();

// ❌ get()/join() without reason
// ❌ Blocking operations in ForkJoinPool
// ❌ Ignoring thread pool starvation

🎯 Interview Cheat Sheet

Must know:

  • Blocking code stops the thread and waits (get(), sleep(), I/O read)
  • Non-blocking β€” thread is free, callback on completion (CompletableFuture, NIO)
  • Signs of blocking: get/join/sleep/await, synchronous I/O, no Async in name (but not guaranteed)
  • Hidden blocking: method called Async but internally calls a blocking operation, blocking ForkJoinPool
  • Thread pool starvation: all pool threads are blocked, new tasks wait

Frequent follow-up questions:

  • How to distinguish blocking from non-blocking? β€” blocking does not return control immediately, non-blocking returns Future/CF immediately
  • Does CompletableFuture.get() block? β€” Yes, the thread transitions to WAITING
  • What is hidden blocking? β€” An Async method internally calls a blocking operation, blocking the ForkJoinPool
  • Do Virtual Threads solve the problem? β€” Yes, blocking suspends the virtual thread, not the OS thread (Java 21+)

Red flags (DO NOT say):

  • β€œget() in a CompletableFuture chain is normal practice” β€” this is blocking, destroys async
  • β€œA method called Async means non-blocking” β€” may call get/join internally
  • β€œBlocking in ForkJoinPool is not a problem” β€” thread pool starvation for all tasks

Related topics:

  • [[15. Why is it important to avoid blocking operations in CompletableFuture]]
  • [[12. What thread pool is used by default for async methods]]
  • [[11. What is the difference between thenApply() and thenApplyAsync()]]
  • [[13. How to specify your own Executor for CompletableFuture]]