Backend

99 Ngày Java — Ngày 67: Concurrent collections

SSite Admin
2 tháng 10, 2026 2 phút đọc 4 lượt xem
99 Ngày Java — Ngày 67: Concurrent collections

Một HashMap dùng chung không tự an toàn khi nhiều luồng ghi. Java cung cấp collection dành cho đồng thời, nhưng mỗi loại tối ưu một kiểu truy cập khác nhau. Chọn đúng cấu trúc thường đơn giản hơn tự khóa mọi thao tác; vẫn phải hiểu phạm vi nguyên tử và giới hạn bộ nhớ.

Concurrent collections

1. ConcurrentHashMap và thao tác nguyên tử

ConcurrentHashMap cho nhiều luồng đọc/ghi mà không khóa toàn bộ map. Tránh chuỗi containsKey rồi put: giữa hai bước, luồng khác có thể sửa dữ liệu. Dùng putIfAbsent, computeIfAbsent hoặc merge cho một khóa. Hàm mapping nên ngắn, không sửa chính map đó và không phụ thuộc hiệu ứng phụ.

ConcurrentHashMap<String, LongAdder> counts =
    new ConcurrentHashMap<>();
counts.computeIfAbsent("ok", k -> new LongAdder())
    .increment();

2. CopyOnWriteArrayList cho đọc nhiều

Mỗi lần thay đổi tạo bản sao mảng bên dưới; iterator thấy snapshot tại thời điểm bắt đầu và không phản ánh thay đổi mới. Hợp với danh sách listener nhỏ, đọc rất nhiều và sửa hiếm. Với danh sách lớn hoặc ghi liên tục, chi phí sao chép và cấp phát sẽ cao.

CopyOnWriteArrayList<Listener> listeners =
    new CopyOnWriteArrayList<>();
listeners.add(listener);
for (Listener each : listeners) each.onEvent(event);

3. BlockingQueue và backpressure

BlockingQueue nối producer với consumer; put chờ khi queue đầy, take chờ khi rỗng. Chọn ArrayBlockingQueue có capacity hữu hạn để tải tăng không ăn hết bộ nhớ. Không tạo một worker cho mỗi item. Khi dừng, dùng interrupt hoặc một tín hiệu kết thúc có thiết kế; luôn quyết định xử lý item còn trong queue.

BlockingQueue<Job> jobs = new ArrayBlockingQueue<>(100);
jobs.put(job);       // waits if full
Job next = jobs.take(); // waits if empty

4. An toàn của collection không phủ cả workflow

Một lệnh của ConcurrentHashMap an toàn không có nghĩa hai lệnh liên tiếp là một giao dịch. Ví dụ chuyển điểm giữa hai key cần một bất biến chung; hãy dùng khóa nghiệp vụ, database transaction hoặc thiết kế lại dữ liệu. Đo contention và throughput thực tế trước khi đổi cấu trúc.

  • Map: cập nhật từng key; copy-on-write: đọc nhiều, sửa ít; queue: nối producer và consumer.

  • Bài tập: giới hạn queue rồi đo thời gian producer phải chờ khi consumer chậm.

Concurrent collection giúp giảm khóa thủ công, nhưng không thay thế thiết kế bất biến xuyên nhiều thao tác. Ngày 68 ta xem virtual threads để xử lý nhiều công việc chờ I/O.

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

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

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

  • https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/BlockingQueue.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 67: Custom validation

Tự viết Bean Validation constraint, kiểm tra liên trường và trả lỗi gắn đúng field.

2 thg 10, 20262 phút6
99 Ngày Spring — Ngày 66: Gọi API ngoài bằng RestClient và WebClient

Chọn client đồng bộ hoặc bất đồng bộ, đặt timeout, xử lý lỗi và retry có kiểm soát.

1 thg 10, 20262 phút2
99 Ngày Java — Ngày 66: Lock và deadlock

Dùng ReentrantLock, tryLock và thứ tự khóa cố định để tránh deadlock trên Java 21.

1 thg 10, 20262 phút1