Quay lại danh sách
Java
2024-04-05
30 phút đọc

Concurrency trong Java: Từ Thread truyền thống đến Virtual Threads

Concurrency trong Java: Sự tiến hóa

Xử lý đa luồng luôn là một trong những phần khó nhất nhưng cũng thú vị nhất của Java. Nếu không nắm rõ mô hình concurrency, bạn rất dễ tạo ra những bug “ma” khó reproduce, hoặc lãng phí tài nguyên máy chủ.

1. Platform Threads (Truyền thống)

Mỗi luồng Java tương ứng với một luồng hệ điều hành (OS Thread). Điều này rất tốn kém tài nguyên (mất khoảng 1MB bộ nhớ cho mỗi stack). Bạn không thể tạo ra 1 triệu luồng như vậy.

Platform Threads phù hợp với các ứng dụng desktop, hoặc server với số lượng kết nối vừa phải. Nhưng khi bước vào thế giới microservices, hàng chục nghìn kết nối đồng thời là chuyện bình thường.

2. Thread Pool (ExecutorService)

Để giải quyết vấn đề trên, chúng ta dùng Thread Pool. Tuy nhiên, nó vẫn bị giới hạn. Nếu một task phải chờ I/O (như gọi database), luồng đó sẽ bị block, gây lãng phí CPU.

Một ví dụ đơn giản về Thread Pool:

ExecutorService executor = Executors.newFixedThreadPool(10);

for (int i = 0; i < 100; i++) {
    int index = i;
    executor.submit(() -> {
        System.out.println("Task " + index + " running on " + Thread.currentThread().getName());
    });
}

executor.shutdown();

Thread Pool phù hợp cho các tác vụ CPU bound hoặc số lượng task không quá khổng lồ. Nhưng khi bạn cần xử lý hàng chục nghìn kết nối I/O đồng thời, mô hình này bắt đầu lộ hạn chế.

3. Virtual Threads (Java 21+)

Đây là "game changer". Virtual Threads cực kỳ nhẹ (chỉ tốn vài KB bộ nhớ). Khi một Virtual Thread bị block bởi I/O, Java runtime sẽ tự động ngắt nó ra khỏi OS Thread và cho task khác vào chạy.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -> {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return i;
        });
    });
}

Đoạn code trên có thể chạy 10,000 task song song chỉ trong tích tắc mà không làm "treo" máy.

4. Khi nào nên dùng Virtual Threads?

  • Khi bạn xây dựng server xử lý rất nhiều request I/O bound (HTTP, database, message queue,...).
  • Khi bạn muốn giữ style code đồng bộ (blocking) nhưng vẫn đạt hiệu năng như async non-blocking.
  • Khi bạn đang migrate từ code multi-threading cũ sang mô hình mới mà không muốn viết lại toàn bộ theo phong cách reactive.

5. Kết nối với kiến trúc tổng thể

Virtual Threads không thay thế mọi thứ, nhưng nó mở ra cách tiếp cận mới:

  • Dùng Virtual Threads cho tầng service / controller.
  • Dùng thread pool/bare threads cho các tác vụ cần kiểm soát chặt tài nguyên.
  • Kết hợp với structured concurrency để quản lý vòng đời task rõ ràng hơn.

Kỷ nguyên mới của Java đã đến, nơi chúng ta có thể viết mã đồng bộ nhưng thực thi bất đồng bộ một cách cực kỳ hiệu quả. Hiểu rõ các mô hình concurrency giúp bạn chọn đúng công cụ cho từng bài toán và khai thác tối đa sức mạnh của JVM.