Module 05 · Executable

Java NIO, Non-blocking I/O & Event Loop

Giải thích cơ chế readiness, Selector và event loop từ Java NIO để chuẩn bị cho Reactor Netty và Vert.x.


1. Mục tiêu

  • Hiểu blocking I/O giữ execution context như thế nào.
  • Hiểu Selector multiplex nhiều SelectableChannel.
  • Hiểu readiness-based processing và event loop.
  • Không nhầm event loop với “một thread thần kỳ”.
  • Biết tại sao blocking handler phá tail latency.

2. Blocking I/O

byte[] data = socket.getInputStream().readNBytes(1024);

Nếu chưa có dữ liệu, blocking read có thể giữ thread ở trạng thái chờ. Với mô hình thread-per-connection/request, số lượng waiting connections thường kéo theo nhu cầu nhiều execution contexts.

Thread A ── read(socket A) ── WAIT
Thread B ── read(socket B) ── WAIT
Thread C ── read(socket C) ── WAIT

3. NIO readiness model

Java NIO cho phép channel hoạt động non-blocking và đăng ký với Selector. Selector là multiplexor của nhiều SelectableChannel.

SocketChannel A ─┐
SocketChannel B ─┼──► Selector ──► ready keys
SocketChannel C ─┘

Mỗi registration được biểu diễn bằng SelectionKey. Interest set nói ta quan tâm operation nào; ready set cho biết operation nào hiện sẵn sàng.

4. Minimal Selector loop

try (Selector selector = Selector.open();
     ServerSocketChannel server = ServerSocketChannel.open()) {

    server.configureBlocking(false);
    server.bind(new InetSocketAddress(8080));
    server.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
        selector.select();

        Iterator<SelectionKey> it =
            selector.selectedKeys().iterator();

        while (it.hasNext()) {
            SelectionKey key = it.next();
            it.remove();

            if (key.isAcceptable()) {
                // accept connection, configure non-blocking,
                // register OP_READ
            } else if (key.isReadable()) {
                // read available bytes without blocking the loop
            }
        }
    }
}
Đây là code học mental model, không phải khuyến nghị tự viết production HTTP server thay cho Netty/Vert.x/Reactor Netty.

5. Event Loop

while (running) {
    events = waitForReadyEvents();

    for (event : events) {
        handle(event);   // handler phải ngắn và non-blocking
    }

    runReadyCallbacks();
}

Event loop tận dụng việc phần lớn thời gian của network application là chờ I/O. Thay vì dành một thread cho mỗi request đang chờ, một số ít event-loop threads xử lý các event/callback khi resource đã sẵn sàng.

6. Những khái niệm không đồng nghĩa

Phát biểuKết luận
Async = multi-threadedSai. Async mô tả quan hệ thời gian/control flow, không bắt buộc nhiều thread.
Non-blocking = parallelSai. Một event loop một thread vẫn có thể non-blocking.
Event loop = Reactive StreamsSai. Event loop là execution/I/O model; Reactive Streams là protocol có backpressure.
Reactive = event loopSai. Có liên quan nhưng abstraction khác nhau.

7. Golden Rule — Never block the event loop

t=0ms    event loop xử lý Request A
t=2ms    A gọi Thread.sleep(1000)
         ├─ Request B READY nhưng không được xử lý
         ├─ Request C READY nhưng không được xử lý
         └─ Timer D READY nhưng không được xử lý
t=1002ms loop mới tiếp tục

Một handler block 1 giây không chỉ làm request A chậm; nó trì hoãn mọi task cùng event loop. Đó là lý do tail latency (p95/p99) có thể tăng mạnh.

8. CPU-bound work

Non-blocking I/O không giải quyết CPU saturation. Nếu handler chạy tính toán 500 ms trên event-loop thread, loop vẫn bị chiếm.

// anti-pattern trong event-loop handler
BigInteger result = expensivePrimeSearch(input);

Cần offload sang worker/compute executor hoặc thiết kế job system tùy framework.

9. Thread Pool vs Virtual Threads vs Event Loop

ModelExecution styleĐiểm mạnhĐiểm phải giữ kỷ luật
Platform poolBlocking/imperativeĐơn giản, bounded resourcesPool/queue saturation
Virtual threadsBlocking/imperativeConcurrency rất lớn cho I/O waitDownstream capacity, pinning, CPU
Event loopNon-blocking/callback-Future-reactiveÍt threads cho nhiều connectionsKhông block loop; control flow phức tạp hơn

10. Lab A — Blocking event loop simulator

Dùng một single-thread executor đóng vai event loop:

ExecutorService eventLoop =
    Executors.newSingleThreadExecutor();

for (int i = 0; i < 20; i++) {
    int requestId = i;
    eventLoop.submit(() -> {
        long start = System.currentTimeMillis();

        if (requestId == 3) {
            Thread.sleep(1000); // inject blocking
        }

        System.out.printf(
            "request=%d latency=%dms thread=%s%n",
            requestId,
            System.currentTimeMillis() - start,
            Thread.currentThread().getName()
        );
        return null;
    });
}

Đo thời điểm request 4–19 được xử lý. Sau đó chuyển blocking task sang worker pool và post completion trở lại event loop.

11. Lab B — NIO echo server

  1. Tạo non-blocking ServerSocketChannel.
  2. Register OP_ACCEPT.
  3. Mỗi accepted SocketChannel register OP_READ.
  4. Khi readable, đọc vào ByteBuffer và echo.
  5. In số connections và thread name.
  6. Mở nhiều clients để quan sát một selector thread multiplex nhiều channels.

12. Lab C — Tail latency

Tạo 1.000 task rất nhanh (1 ms) xen mỗi 100 task một blocking task 100 ms. Tính:

  • median/p50
  • p95
  • p99
  • max latency

Giải thích vì sao average latency có thể trông “không quá tệ” nhưng p99 tăng mạnh.

13. Bài tập

E1. Selector có tự chạy callback không?
Không. Selector báo readiness; application/event loop vẫn phải iterate key và thực thi logic.
E2. Một event loop có 4 threads nghĩa là chỉ xử lý được 4 requests đồng thời?
Không. Nếu I/O non-blocking, mỗi thread có thể multiplex rất nhiều connections; giới hạn thật phụ thuộc handler time, I/O, buffers, downstream và system resources.
E3. Vì sao JDBC truyền thống là vấn đề trong event-loop handler?
E4. CPU task 300 ms nên đưa vào reactive pipeline thế nào về mặt execution model?

14. Cầu nối sang Reactive Streams

Đến đây ta đã có hai vấn đề còn thiếu abstraction:

  1. Async/event-loop giúp không chặn thread khi chờ I/O.
  2. Nhưng nếu producer phát dữ liệu nhanh hơn consumer, hệ thống cần cơ chế điều tiết tốc độ.
Non-blocking I/O
      +
Async callbacks/Futures
      +
Demand control
      ↓
Reactive Streams / Backpressure

Đó là module tiếp theo trong learning path.

15. Interview checkpoint

  1. Selector làm gì?
  2. SelectionKey đại diện cho gì?
  3. Interest set và ready set khác nhau thế nào?
  4. Event loop khác thread pool?
  5. Tại sao blocking 100 ms có thể làm p99 tăng rất lớn?
  6. Non-blocking có đồng nghĩa không dùng thread không?
  7. Virtual Threads và Event Loop giải cùng bài toán theo hai hướng nào?

Tài liệu và code thực hành

Đọc chapter chi tiết [legacy source: modules/05-nio-event-loop.md] · Mở foundation labs [legacy source: labs/foundation/README.md] · Xem Java source [legacy source: examples/README.md]