Backend

99 Ngày Java — Ngày 63: volatile và Atomic

SSite Admin
28 tháng 09, 2026 3 phút đọc 0 lượt xem
99 Ngày Java — Ngày 63: volatile và Atomic

Sau synchronized, hai câu hỏi dễ bị lẫn: luồng khác có nhìn thấy giá trị mới không, và thao tác cập nhật có bị xen vào giữa không? volatile giải quyết vấn đề visibility của một trường; AtomicInteger cung cấp các cập nhật nguyên tử cho một giá trị. Chúng không thay thế khóa khi nhiều trường phải cùng đúng một invariant.

volatile và Atomic

1. Visibility và happens-before

Không có đồng bộ, JVM và CPU được phép tối ưu đọc/ghi khiến luồng khác không thấy cập nhật vào lúc bạn mong đợi. Ghi vào một trường volatile happens-before lần đọc sau đó thấy ghi ấy. Quan hệ này cũng áp dụng cho các ghi trước volatile write trong cùng luồng. Nó là quy tắc về thứ tự/visibility, không phải lời hứa rằng hai luồng thay nhau chạy công bằng.

final class Worker implements Runnable {
    private volatile boolean running = true;
    public void stop() { running = false; }
    public void run() {
        while (running) { doOneBoundedTask(); }
    }
    private void doOneBoundedTask() { /* finite work */ }
}

2. volatile không biến ++ thành nguyên tử

volatile int count làm mỗi lần đọc và ghi có quy tắc visibility, nhưng count++ vẫn là đọc, cộng, ghi. Hai luồng vẫn mất cập nhật như ngày 62. Chọn volatile cho cờ trạng thái đơn giản hoặc snapshot tham chiếu; khi cần cập nhật phụ thuộc giá trị cũ, dùng AtomicInteger hoặc khóa.

volatile int count = 0;
// NOT atomic:
count++;
// A volatile write is visible; the compound increment can still race.

3. AtomicInteger và compare-and-set

incrementAndGet() cập nhật một bộ đếm nguyên tử mà không cần synchronized ở phía gọi. compareAndSet(expected, update) chỉ ghi khi giá trị hiện tại vẫn bằng expected; thất bại nghĩa là ai đó đã đổi giá trị, caller phải đọc lại và quyết định thử tiếp. CAS rất hữu ích nhưng vòng lặp nhiều tranh chấp có thể tốn CPU.

import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger count = new AtomicInteger();
int next = count.incrementAndGet(); // one atomic update

AtomicInteger stock = new AtomicInteger(3);
int seen;
do {
    seen = stock.get();
    if (seen == 0) break; // sold out
} while (!stock.compareAndSet(seen, seen - 1));

4. Chọn công cụ theo invariant

AtomicInteger bảo vệ từng thao tác trên chính nó; nếu cần bảo đảm balance giảm đồng thời orderCount tăng, hai atomic riêng lẻ không tạo một giao dịch chung. Khóa của ngày 62 hoặc cấu trúc dữ liệu đồng thời phù hợp sẽ dễ chứng minh hơn. Với tác vụ dừng qua interrupt, vẫn giữ quy tắc ngày 61: đừng nuốt InterruptedException.

  • Bài tập: thay volatile int count++ bằng AtomicInteger và đo lại 10.000 tác vụ.

  • Bài tập: thiết kế hai trường phải cập nhật cùng nhau; giải thích vì sao cần một vùng khóa.

Ghi nhớ: volatile là visibility và ordering; atomic là cập nhật không bị xen vào giữa cho một giá trị; synchronized bảo vệ một vùng bất biến lớn hơn. Ngày 64 sẽ quản lý luồng bằng ExecutorService thay vì tự tạo từng Thread.

Tài liệu đối chiếu

  • https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.html

  • https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/atomic/AtomicInteger.html

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 63: Application events

Publish event trong Spring, @EventListener đồng bộ, @TransactionalEventListener sau commit và giới hạn độ bền.

28 thg 9, 20262 phút0
99 Ngày Spring — Ngày 62: Custom annotation kết hợp AOP

Tạo @Timed bằng metadata runtime, chọn method với @annotation, đo thời gian bằng @Around và giữ nguyên kết quả, lỗi.

27 thg 9, 20262 phút6
99 Ngày Java — Ngày 62: Race condition và synchronized

Vì sao counter++ mất cập nhật, cách synchronized bảo vệ shared state, intrinsic lock và các bẫy về phạm vi khóa.

27 thg 9, 20263 phút6