Java Concurrency Foundation
Nền tảng bắt buộc trước async/reactive: threads, synchronization, pools, deadlock và Virtual Threads.
1. Mục tiêu học
- Phân biệt concurrency và parallelism.
- Nhìn được chi phí của platform thread và context switching.
- Hiểu race condition, visibility, atomicity.
- Biết dùng Executor/ThreadPoolExecutor theo tư duy resource management.
- Hiểu Virtual Threads giải quyết bài toán gì và không giải quyết gì.
2. Mental model: server truyền thống
Request A ──► Thread A ──► DB call ──[WAIT]──► response Request B ──► Thread B ──► API call ─[WAIT]──► response Request C ──► Thread C ──► CPU work ─────────► response
Điểm quan trọng: khi một platform thread block vì I/O, OS thread bên dưới vẫn là resource bị giữ cho computation đó. Nhiều I/O wait có thể dẫn tới nhiều threads, scheduling overhead và memory footprint.
3. Concurrency vs Parallelism
| Khái niệm | Ý nghĩa | Ví dụ |
|---|---|---|
| Concurrency | Nhiều công việc đang tiến triển trong cùng khoảng thời gian. | Một CPU core xen kẽ xử lý nhiều task. |
| Parallelism | Nhiều công việc thực thi thực sự cùng lúc. | 4 CPU core chạy 4 computation đồng thời. |
Một chương trình có thể concurrent nhưng không parallel. Reactive/event-loop thường tối ưu concurrency cho I/O; CPU-heavy work vẫn cần tài nguyên tính toán thực.
4. Thread lifecycle và interrupt
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
doSmallUnitOfWork();
}
});
worker.start();
worker.interrupt();
worker.join();
interrupt() là cơ chế cooperative cancellation, không phải “kill thread”. Code phải tôn trọng interrupt hoặc xử lý InterruptedException đúng cách.
5. Race condition, atomicity, visibility
class Counter {
private int value = 0;
void increment() { value++; }
int get() { return value; }
}
value++ không phải một thao tác atomic duy nhất. Nhiều thread có thể đọc cùng giá trị rồi ghi đè kết quả của nhau.
Sửa bằng synchronized
class Counter {
private int value;
synchronized void increment() { value++; }
synchronized int get() { return value; }
}
volatile dùng cho visibility, không biến compound operation thành atomic
volatile boolean running = true;
volatile int value; value++; vẫn có race condition.6. Executor và Thread Pool
Executor tách “submit task” khỏi cơ chế thực thi. ThreadPoolExecutor giúp giới hạn và quản lý resource.
ExecutorService pool = new ThreadPoolExecutor(
4, 8,
30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
Luồng quyết định đơn giản
submit task │ ├─ core threads còn chỗ? ─► create/use worker │ ├─ queue còn chỗ? ────────► enqueue │ ├─ chưa tới max? ─────────► create extra worker │ └─ hết capacity ──────────► rejection policy
Pool không chỉ là performance trick; nó là cơ chế resource bounding.
7. Deadlock
synchronized (lockA) {
synchronized (lockB) { ... }
}
// thread khác lấy lockB rồi lockA => nguy cơ deadlock
Phòng tránh bằng lock ordering nhất quán, giảm critical section, timeout lock khi phù hợp và dùng thread dump để chẩn đoán.
8. Virtual Threads
Virtual Thread là lightweight Java thread được JVM scheduling lên platform threads. Mục tiêu chính là giúp mô hình thread-per-task/thread-per-request scale tốt hơn cho workload nhiều blocking I/O.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> a = executor.submit(() -> callServiceA());
Future<String> b = executor.submit(() -> callServiceB());
System.out.println(a.get() + b.get());
}
So sánh sơ bộ
| Model | Ưu điểm | Trade-off |
|---|---|---|
| Platform thread pool | Quen thuộc, kiểm soát resource rõ. | Nhiều blocking task có thể cần nhiều OS threads. |
| Virtual Threads | Imperative code, concurrency lớn cho blocking I/O. | Vẫn cần hiểu resource downstream; CPU/pinning/lock contention không biến mất. |
| Event Loop | Ít threads, hiệu quả cho non-blocking I/O. | Blocking event loop gây tail latency nghiêm trọng. |
9. Lab — Saturation của thread pool
Tạo pool 2 threads, queue 2, rồi submit 10 task ngủ 2 giây. In timestamp, thread name và task ID.
ExecutorService pool = new ThreadPoolExecutor(
2, 2, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i < 10; i++) {
int id = i;
try {
pool.submit(() -> {
System.out.printf("%s task=%d%n",
Thread.currentThread().getName(), id);
Thread.sleep(2000);
return null;
});
} catch (RejectedExecutionException e) {
System.out.println("REJECTED task=" + id);
}
}
Quan sát: task nào chạy, task nào queue, task nào bị reject. Sau đó đổi sang CallerRunsPolicy và giải thích backpressure “thô” xuất hiện như thế nào ở phía producer.
10. Bài tập
volatile int counter không đủ cho counter++?11. Interview checkpoint
- Concurrency khác parallelism thế nào?
- Context switching có chi phí gì?
synchronizedgiải quyết visibility và atomicity ra sao?volatilephù hợp cho pattern nào?- Tại sao bounded queue quan trọng?
- Virtual Threads khác thread pool truyền thống ở đâu?
- Virtual Threads có làm CPU-bound code nhanh hơn không?
- Khi nào event loop có lợi hơn blocking thread model?
12. Checklist
- ☑ Hiểu thread là resource, không chỉ là syntax.
- ☑ Biết pool = scheduling + bounding.
- ☑ Không nhầm volatile với atomicity.
- ☑ Hiểu virtual thread tối ưu concurrency cho blocking I/O.
- ☑ Sẵn sàng học Future/CompletableFuture.
Tài liệu và code thực hành
Đọc chapter chi tiết [legacy source: modules/03-java-concurrency.md] · Mở foundation labs [legacy source: labs/foundation/README.md] · Xem Java source [legacy source: examples/README.md]