Module 01 — Tổng kết & cheat sheet
Một trang để bookmark: cheat sheet 15 công cụ concurrency, glossary 18 thuật ngữ, 7 pitfall lớn nhất kèm code sai/đúng, và self-assessment trước khi sang module I/O & NIO.
TL;DR: Module 01 đi từ "vì sao race condition xảy ra" tới "tổ chức cả hệ thống concurrent thế nào", qua 17 bài và 5 phiên bản TicketFlow. Trang này gom tất cả thành một chỗ tra cứu nhanh: bảng chọn công cụ theo tình huống, glossary thuật ngữ, các pitfall hay gặp nhất kèm code sai → đúng, và checklist tự đánh giá. Bookmark nó — sáu tháng sau khi cần chọn giữa ReentrantLock và ConcurrentHashMap.compute, đây là trang để quay lại.
Đã đi qua những gì
Sợi chỉ xuyên suốt module là một hệ thống đặt vé. TicketFlow v0 mở màn ở bài thread safety với một BookingService chạy hoàn hảo đơn luồng nhưng oversell dưới tải, vì book là một cụm check-then-act không nguyên tử trên HashMap dùng chung. Ta thử né vấn đề bằng confinement (dồn đặt vé vào một thread duy nhất) và immutability (Event, Booking thành record), rồi nhận ra số vé đã bán vốn dĩ vừa shared vừa mutable — không né được, phải canh gác. v1 vá race bằng Java Monitor Pattern, sau đó thử các biến thể: bộ đếm AtomicInteger lock-free, ReentrantLock để có tryLock và timeout, read-write lock cho tồn kho đọc nhiều ghi ít. v2 chuyển hẳn sang delegation — ConcurrentHashMap.compute cho atomicity per-key — và dựng pipeline producer–consumer qua BlockingQueue với poison pill và interrupt để shutdown sạch. v3 nâng tầng kiến trúc: booking pool dựng tay từ ThreadPoolExecutor với queue bounded và CallerRunsPolicy làm backpressure, luồng validate → book → notify ghép bằng CompletableFuture, báo cáo đối soát chạy fork/join. Cuối cùng v4 đổi tầng request sang virtual thread — một thread mỗi request, thoải mái block ở I/O — và bọc fan-out kiểm tồn kho + xác thực thanh toán vào StructuredTaskScope với ScopedValue mang context. Công cụ đổi qua từng phiên bản, nhưng câu hỏi trung tâm thì không: dữ liệu nào đang shared, dữ liệu nào mutable, và ai đang canh gác nó.
flowchart LR
V0["v0<br/>HashMap<br/>race oversell"] --> V1["v1<br/>monitor / atomic /<br/>explicit lock"]
V1 --> V2["v2<br/>CHM compute +<br/>BlockingQueue"]
V2 --> V3["v3<br/>ThreadPoolExecutor +<br/>CompletableFuture"]
V3 --> V4["v4<br/>virtual thread +<br/>structured concurrency"]🗺️ Cheat sheet
| Công cụ | Khi nào dùng | Pitfall lớn nhất |
|---|---|---|
synchronized | Compound action trên shared state, contention thấp, cần đơn giản và tự nhả khóa | Khóa quá rộng — ôm cả I/O dài trong khối khóa, serialize mọi thứ (bài 06, 16) |
volatile | Cờ trạng thái một writer nhiều reader; publish tham chiếu tới immutable holder | Dùng cho read-modify-write kiểu count++ — visibility có nhưng atomicity không (bài 06) |
AtomicInteger / AtomicReference | Thao tác nguyên tử trên đúng một biến, lock-free, contention vừa | Invariant trải nhiều biến — CAS từng biến không gói được cả cụm (bài 07) |
LongAdder | Bộ đếm ghi nhiều đọc ít dưới contention cao (metrics, counter tải) | Đọc sum() không phải snapshot nguyên tử tại một thời điểm (bài 07) |
ReentrantLock | Cần thứ synchronized không có: tryLock, timeout, interruptible, fairness, nhiều Condition | Quên unlock() trong finally — exception làm khóa kẹt vĩnh viễn (bài 08) |
ReadWriteLock | Đọc áp đảo ghi (tỷ lệ hàng trăm trên một), đoạn đọc đủ dài để đáng giá | Writer starvation và overhead — đọc ngắn thì chậm hơn cả khóa thường (bài 09) |
StampedLock | Read cực nhiều, cần optimistic read rẻ hơn cả read lock | Không reentrant; quên validate() sau optimistic read là đọc dữ liệu rách (bài 09) |
ConcurrentHashMap | Map dùng chung; compute/putIfAbsent cho compound action nguyên tử per-key | Tưởng map thread-safe là mọi cụm get-rồi-put cũng tự an toàn (bài 10) |
BlockingQueue | Tách nhịp producer khỏi consumer; hàng đợi việc có backpressure | Dùng unbounded — backpressure chết, memory tích nợ tới OOM (bài 11) |
Latch / Barrier / Semaphore | Điều phối tiến độ: chờ N việc xong, hẹn nhau tại một điểm, giới hạn truy cập đồng thời | Lẫn vai: synchronizer điều phối tiến độ, không bảo vệ dữ liệu (bài 12) |
ThreadPoolExecutor | Chạy nhiều task trên platform thread; cần kiểm soát size, queue, rejection | Executors.newFixedThreadPool mang queue không giới hạn — OOM âm thầm (bài 13) |
CompletableFuture | Pipeline async nhiều bước: compose, combine, allOf, timeout, xử lý lỗi | Quên executor cho *Async (nghẹt commonPool) và pipeline nuốt exception (bài 14) |
ForkJoinPool | Divide-and-conquer thuần CPU trên dữ liệu lớn; nền của parallel stream | Task blocking trong common pool — bỏ đói cả JVM dùng chung pool đó (bài 15) |
| Virtual thread | Workload I/O-bound cần rất nhiều task đồng thời, thread-per-request | Pool virtual thread (vô nghĩa) và pinning do native frame / synchronized trên Java 21 (bài 16) |
ScopedValue | Truyền context bất biến qua scope và các nhánh structured concurrency | Mang thói quen ThreadLocal mutable sang — ScopedValue là immutable, rebind theo scope (bài 17) |
📖 Glossary module
| Thuật ngữ | Một câu | Nguồn |
|---|---|---|
| Race condition | Kết quả đúng/sai phụ thuộc vào thứ tự xen kẽ may rủi của các thread truy cập shared state. | Bài 03 |
| Atomicity | Tính chất một thao tác diễn ra trọn vẹn hoặc không diễn ra, không có trạng thái dở dang quan sát được. | Bài 03 |
| Visibility | Bảo đảm thay đổi của thread này được thread khác nhìn thấy — không tự có, phải qua synchronization. | Bài 03 |
| Happens-before | Quan hệ thứ tự của Java Memory Model: nếu A happens-before B thì B thấy mọi ghi của A. | Bài 03 |
| Check-then-act | Compound action kiểm tra rồi hành động dựa trên kết quả kiểm — cửa sổ giữa hai bước là chỗ race chui vào. | Bài 03 |
| Confinement | Đạt thread safety bằng cách không chia sẻ: dữ liệu chỉ một thread chạm tới. | Bài 04 |
| Immutability | Đạt thread safety bằng cách không thay đổi: object đóng băng sau khởi tạo, đọc lúc nào cũng như nhau. | Bài 05 |
| Monitor (intrinsic lock) | Khóa gắn sẵn trong mỗi object Java mà synchronized giành và nhả, kèm wait-set cho wait/notify. | Bài 06 |
| Cooperative cancellation | Hủy thread kiểu hợp tác: bên ngoài đặt cờ interrupt, thread tự kiểm và thoát có trật tự. | Bài 02 |
| CAS (compare-and-swap) | Chỉ thị nguyên tử của CPU: ghi giá trị mới với điều kiện giá trị cũ chưa bị ai đổi, thất bại thì retry. | Bài 07 |
| ABA problem | CAS bị lừa khi giá trị đổi từ A sang B rồi về lại A — trông "chưa đổi" nhưng đã qua hai lần ghi. | Bài 07 |
| AQS | AbstractQueuedSynchronizer — bộ khung hàng đợi chờ mà ReentrantLock, Semaphore, CountDownLatch cùng xây lên trên. | Bài 09 |
| Backpressure | Áp lực truyền ngược từ nơi tiêu thụ chậm về nơi sản xuất nhanh, buộc hệ thống chậm lại thay vì tích nợ. | Bài 11 |
| Poison pill | Phần tử sentinel bỏ vào queue làm tín hiệu "hết việc" để consumer thoát vòng lặp một cách sạch sẽ. | Bài 11 |
| Work stealing | Thread nhàn rỗi chủ động lấy task từ deque của thread bận — cơ chế cân tải của ForkJoinPool. | Bài 15 |
| Carrier thread | Platform thread trong pool nhỏ mà JVM mount virtual thread lên để chạy. | Bài 16 |
| Pinning | Virtual thread block mà không unmount được (native frame; synchronized trên Java 21) — ghim chặt carrier. | Bài 16 |
| Structured concurrency | Kỷ luật buộc vòng đời các task con nằm gọn trong một scope: cha không thoát khi con còn chạy, lỗi và hủy lan có trật tự. | Bài 17 |
⚠️ Pitfall tổng hợp
Bảy lỗi gây ra phần lớn sự cố concurrency trong code production — tất cả đều đã xuất hiện trong module, gom lại đây để soi code review.
1. Check-then-act trên collection "đã thread-safe" (bài 03, 10). Đổi HashMap thành ConcurrentHashMap không cứu được cụm thao tác.
// SAI — tung thao tac an toan, ca cum van race
if (sold.get(id) < cap) sold.put(id, sold.get(id) + 1);
// DUNG — compound action nguyen tu per-key
sold.compute(id, (k, v) -> v == null || v >= cap ? v : v + 1);
2. volatile cho read-modify-write (bài 03, 06). volatile chỉ cho visibility, không cho atomicity.
// SAI — hai thread cung cong van mat lan tang
private volatile long revenue;
revenue += amount;
// DUNG — contention cao thi LongAdder
private final LongAdder revenue = new LongAdder();
revenue.add(amount);
3. Quên unlock trong finally (bài 08). ReentrantLock không tự nhả như synchronized; một exception là khóa kẹt vĩnh viễn.
// SAI — exception giua chung lam moi thread sau cho mai mai
lock.lock();
doWork();
lock.unlock();
// DUNG
lock.lock();
try { doWork(); } finally { lock.unlock(); }
4. Nuốt InterruptedException (bài 02, 11, 13). Nuốt im lặng là xóa sổ một yêu cầu hủy — pool có thể không bao giờ tắt được.
// SAI — tin hieu huy bien mat
try { task = queue.take(); } catch (InterruptedException e) { }
// DUNG — khoi phuc co roi thoat co trat tu
try { task = queue.take(); }
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
5. Pool với queue không giới hạn (bài 11, 13). Executors.newFixedThreadPool dùng LinkedBlockingQueue unbounded: pool không bao giờ từ chối, chỉ âm thầm tích nợ tới OOM.
// SAI — khong co tran nao cho queue
ExecutorService pool = Executors.newFixedThreadPool(10);
// DUNG — bounded queue + chinh sach khi cham tran
ExecutorService pool = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1_000), new ThreadPoolExecutor.CallerRunsPolicy());
6. CompletableFuture chạy I/O trên commonPool và nuốt exception (bài 14). *Async không truyền executor sẽ chiếm ForkJoinPool.commonPool() — vài task chờ DB là parallel stream toàn JVM vạ lây; pipeline không có stage lỗi thì task hỏng không một dòng log.
// SAI — blocking I/O tren commonPool, loi bien mat trong im lang
CompletableFuture.supplyAsync(() -> httpClient.fetch(url));
// DUNG — executor rieng cho I/O + dong chuoi bang noi tieu thu loi
CompletableFuture.supplyAsync(() -> httpClient.fetch(url), ioPool)
.exceptionally(ex -> { log.error("fetch failed", ex); return fallback; });
7. Ôm khóa qua lời gọi I/O dài (bài 06, 16). Khóa để bảo vệ shared mutable state, không phải để serialize lời gọi mạng; trên Java 21 khối synchronized block còn ghim carrier của virtual thread.
// SAI — giu khoa suot mot round-trip mang
synchronized (lock) { return remoteApi.fetch(symbol); }
// DUNG — ReentrantLock, va thu hep khoa ve dung phan shared state
lock.lock();
try { return cache.get(symbol); } finally { lock.unlock(); }
// goi remoteApi.fetch ngoai khoa, roi cap nhat cache trong mot khoa ngan
✅ Self-assessment
Bạn đã đạt module này nếu tự tin tick đủ sáu ô — mỗi ô khớp một learning outcome của module:
- Giải thích được vì sao concurrency bug xảy ra: shared mutable state, atomicity vs visibility, và quan hệ happens-before của Java Memory Model.
- Nếu chưa → đọc lại bài 03 — Thread safety, section atomicity và visibility.
- Chẩn đoán được race condition trong code thật bằng cách trace các interleaving xui xẻo nhất và chỉ ra compound action không nguyên tử.
- Nếu chưa → đọc lại bài 03 — Thread safety (read-modify-write, check-then-act) và tự vẽ timeline hai thread cho
BookingService v0.
- Nếu chưa → đọc lại bài 03 — Thread safety (read-modify-write, check-then-act) và tự vẽ timeline hai thread cho
- Triển khai được class thread-safe theo bốn chiến lược: confinement, immutability, synchronization, và delegation.
- Nếu chưa → đọc lại bài 04 — Confinement, bài 05 — Immutability, bài 06 — volatile & synchronized và bài 10 — Delegation.
- Chọn đúng cơ chế đồng bộ theo pattern truy cập và mức contention — volatile, synchronized, Atomic/LongAdder, ReentrantLock, ReadWriteLock/StampedLock hay ConcurrentHashMap.
- Nếu chưa → đọc lại bài 07 — Atomic & CAS, bài 08 — ReentrantLock, bài 09 — ReadWriteLock & StampedLock, rồi dò ngược cheat sheet ở trên.
- Thiết kế được kiến trúc thực thi tách task khỏi thread: producer–consumer với BlockingQueue, thread pool có backpressure, pipeline async bằng CompletableFuture.
- Nếu chưa → đọc lại bài 11 — Blocking queues, bài 13 — Executor và bài 14 — CompletableFuture.
- Trace được vòng đời một request trên virtual thread — mount/unmount, pinning — và quyết định khi nào di cư từ thread pool sang virtual thread cùng structured concurrency.
- Nếu chưa → đọc lại bài 16 — Virtual threads và bài 17 — Structured concurrency.
🚀 What's next
Module tiếp theo đổi trục: từ "nhiều thread tranh nhau CPU và bộ nhớ" sang "chương trình nói chuyện với thế giới bên ngoài". Module 02 — I/O & NIO bắt đầu từ stream I/O cổ điển, đi qua Path/Files, serialization và những rủi ro bảo mật của nó, để chuẩn bị nền cho NIO. Kiến thức concurrency vừa học sẽ quay lại liên tục ở đó — blocking I/O chính là lý do virtual thread tồn tại, và mô hình selector của NIO là câu trả lời khác cho cùng bài toán "một thread phục vụ nhiều kết nối".
📚 Tài liệu mở rộng
- Sách: Java Concurrency in Practice — Brian Goetz và cộng sự. Nguồn gốc của phần lớn khung khái niệm trong module (shared mutable state, bốn chiến lược, delegation); cũ về API nhưng chưa cũ về tư duy.
- Spec: JLS Chapter 17 — Threads and Locks — định nghĩa hình thức của Java Memory Model và happens-before; đọc sau khi đã vững bài 03 và 06.
- JEP: JEP 444 — Virtual Threads (final Java 21), JEP 491 — Synchronize Virtual Threads without Pinning (Java 24), JEP 505 — Structured Concurrency, JEP 506 — Scoped Values — đọc phần Motivation của mỗi JEP là cách nhanh nhất hiểu "vì sao API này tồn tại".
- Blog: Inside.java — bài viết và podcast của chính team JDK về Loom, concurrency và JVM; lọc theo tag "Loom" để theo dòng tiến hóa virtual thread.
Bài đầu tiên của module kế: Stream I/O cổ điển
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên