Java Internals & Concurrency/Module 01 — Tổng kết & cheat sheet
19/39
Bài 19 / 39~15 phútConcurrency cơ bảnMiễn phí lượt xem

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 ReentrantLockConcurrentHashMap.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ùngPitfall lớn nhất
synchronizedCompound action trên shared state, contention thấp, cần đơn giản và tự nhả khóaKhóa quá rộng — ôm cả I/O dài trong khối khóa, serialize mọi thứ (bài 06, 16)
volatileCờ trạng thái một writer nhiều reader; publish tham chiếu tới immutable holderDùng cho read-modify-write kiểu count++ — visibility có nhưng atomicity không (bài 06)
AtomicInteger / AtomicReferenceThao tác nguyên tử trên đúng một biến, lock-free, contention vừaInvariant trải nhiều biến — CAS từng biến không gói được cả cụm (bài 07)
LongAdderBộ đế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)
ReentrantLockCần thứ synchronized không có: tryLock, timeout, interruptible, fairness, nhiều ConditionQuê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)
StampedLockRead cực nhiều, cần optimistic read rẻ hơn cả read lockKhông reentrant; quên validate() sau optimistic read là đọc dữ liệu rách (bài 09)
ConcurrentHashMapMap dùng chung; compute/putIfAbsent cho compound action nguyên tử per-keyTưởng map thread-safe là mọi cụm get-rồi-put cũng tự an toàn (bài 10)
BlockingQueueTách nhịp producer khỏi consumer; hàng đợi việc có backpressureDù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ờiLẫn vai: synchronizer điều phối tiến độ, không bảo vệ dữ liệu (bài 12)
ThreadPoolExecutorChạy nhiều task trên platform thread; cần kiểm soát size, queue, rejectionExecutors.newFixedThreadPool mang queue không giới hạn — OOM âm thầm (bài 13)
CompletableFuturePipeline async nhiều bước: compose, combine, allOf, timeout, xử lý lỗiQuên executor cho *Async (nghẹt commonPool) và pipeline nuốt exception (bài 14)
ForkJoinPoolDivide-and-conquer thuần CPU trên dữ liệu lớn; nền của parallel streamTask blocking trong common pool — bỏ đói cả JVM dùng chung pool đó (bài 15)
Virtual threadWorkload I/O-bound cần rất nhiều task đồng thời, thread-per-requestPool virtual thread (vô nghĩa) và pinning do native frame / synchronized trên Java 21 (bài 16)
ScopedValueTruyền context bất biến qua scope và các nhánh structured concurrencyMang thói quen ThreadLocal mutable sang — ScopedValue là immutable, rebind theo scope (bài 17)

📖 Glossary module

Thuật ngữMột câuNguồn
Race conditionKế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
AtomicityTí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
VisibilityBả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-beforeQuan 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-actCompound 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 cancellationHủ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 problemCAS 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
AQSAbstractQueuedSynchronizer — 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 pillPhầ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 stealingThread 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 threadPlatform thread trong pool nhỏ mà JVM mount virtual thread lên để chạy.Bài 16
PinningVirtual thread block mà không unmount được (native frame; synchronized trên Java 21) — ghim chặt carrier.Bài 16
Structured concurrencyKỷ 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:

🚀 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

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

Đặt 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