Java Internals & Concurrency/Module 01 — Tổng kết & cheat sheet
24/47
Bài 24 / 47~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 18 công cụ concurrency, glossary 18 thuật ngữ, 7 pitfall lớn nhất kèm code sai/đúng, và self-assessment cuối module.

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 22 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"]
    style V0 fill:#FCA5A5
    style V4 fill:#6EE7B7

🗺️ 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 08, 21)
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 08)
wait / notify (guarded block)Chờ một điều kiện dưới cùng khóa: nhả monitor và treo thread tới khi thread khác báo state đổi (producer–consumer trong một object)Kiểm điều kiện bằng if thay vì vòng while (spurious/stolen wakeup), và notify thay vì notifyAll (bài 09)
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 10)
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 10)
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 11)
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 12)
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 12)
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 13)
CopyOnWriteArrayListList đọc áp đảo, ghi cực hiếm; iterator cần snapshot bất biến không khóa (danh sách listener)Ghi thường xuyên — mỗi ghi chép cả mảng, chi phí O(n) mỗi lần (bài 14)
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 15)
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 16)
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 17)
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 18)
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 19)
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 20, 21)
Di cư thread pool → virtual threadPool platform đang nghẽn vì thread block ở I/O; muốn thread-per-request không giới hạn số threadBỏ trần của pool cũ mà không thay bằng Semaphore tường minh → mất giới hạn concurrency, đánh sập downstream (bài 21)
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 22)

📖 Glossary module

Thuật ngữMột câuNguồn
Shared mutable stateDữ liệu vừa được nhiều thread dùng chung vừa thay đổi được — gốc của mọi bug concurrency.Bài 03
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 04
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 04
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 04
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 05
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 05
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 06
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 07
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 09
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 10
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 10
AQSAbstractQueuedSynchronizer — bộ khung hàng đợi chờ mà ReentrantLock, Semaphore, CountDownLatch cùng xây lên trên.Bài 12
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 15
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 15
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 19
Carrier threadPlatform thread trong pool nhỏ mà JVM mount virtual thread lên để chạy.Bài 20
PinningVirtual thread block mà không unmount được (native frame; synchronized trên Java 21) — ghim chặt carrier.Bài 21
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 22

⚠️ 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 04, 13). Đổ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 05, 08). 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 11). 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, 15, 17). 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 15, 17). 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 18). *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 08, 21). 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 đủ bảy ô — 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