Delegation: tái dùng lớp thread-safe và khi nào nó vỡ
Đạt thread safety bằng delegation cho ConcurrentHashMap: khi nào một component thread-safe là đủ, và khi nào delegation vỡ trước invariant bắc cầu nhiều field.
TL;DR: Cách đạt thread safety ít rủi ro nhất là delegation — giao việc đồng bộ cho một component đã thread-safe (ConcurrentHashMap...) thay vì tự cầm khóa. Delegation đủ khi toàn bộ state mutable của class nằm trong các component thread-safe độc lập và mỗi method chỉ là một thao tác đơn lên một component — như EventRegistry chỉ có một map. Nó vỡ ngay khi class áp một invariant bắc cầu nhiều component: thêm một AtomicInteger count phải luôn bằng map.size() thì dù cả hai field đều thread-safe riêng, khoảng giữa hai lần cập nhật vẫn mở ra một cửa sổ mà invariant bị vi phạm. Đây là phiên bản nhiều-component của bài học "atomic một biến không phải atomic của invariant". Khi delegation hụt hơi, phải tự bọc cụm thao tác liên đới bằng một khóa của chính class.
1. Delegation: mượn thread safety thay vì tự viết
Bài trước cho ta ReentrantLock, Condition, ReadWriteLock, StampedLock — mạnh hơn synchronized, nhưng cái giá là tự cầm lock, tự unlock trong finally, tự suy luận biến nào canh bởi khóa nào. Đúng được, nhưng dễ sai. Có lối bền hơn: đừng tự viết đồng bộ — giao trách nhiệm đồng bộ cho một component đã thread-safe, để class thừa hưởng sự an toàn đó. Ý tưởng này tên là delegation (ủy thác). Series đã chạm nó ở Atomicity: bộ đếm dùng AtomicLong thay long thì phép tăng nguyên tử mà không cần synchronized, vì việc đồng bộ đã giao cho một đối tượng đã kiểm chứng. Building block hay dùng nhất của hướng này nằm sẵn trong java.util.concurrent: các concurrent collection.
Nguyên tắc: nếu toàn bộ state mutable của một class nằm trong những đối tượng đã thread-safe, và class không áp thêm invariant nào lên cách chúng phối hợp, thì class tự động thread-safe mà không cần khóa riêng. Ví dụ trong TicketFlow — catalog sự kiện chỉ đăng ký và tra Event theo id, state vỏn vẹn một map:
public class EventRegistry {
private final ConcurrentMap<String, Event> events = new ConcurrentHashMap<>();
public void register(Event event) {
Objects.requireNonNull(event, "event must not be null");
if (events.putIfAbsent(event.id(), event) != null) {
throw new IllegalArgumentException("Su kien da ton tai: " + event.id());
}
}
public Event find(String eventId) {
return events.get(eventId);
}
}
Không một synchronized nào. EventRegistry thread-safe vì giao trọn việc đồng bộ cho ConcurrentHashMap: mỗi method chỉ là một lời gọi đơn xuống map, mà mỗi lời gọi đó tự nó đã nguyên tử; class không thêm state nào ngoài map nên không có gì để tự canh. So với Monitor Pattern ở volatile & synchronized — nơi BookingService quây mọi method bằng private lock — đây là một sự nhẹ nhõm. Hình dung delegation như nhà hàng thuê bảo vệ đứng cửa: chủ quán không cần tự học võ, chỉ giao việc canh cửa. Chừng nào trật tự gói trong "ai được vào cửa nào", bảo vệ lo trọn; vấn đề chỉ nảy sinh khi an ninh đòi phối hợp nhiều cửa cùng lúc — đúng chỗ delegation hụt hơi ở mục sau.
2. Khi nào delegation đủ, và khi nào nó vỡ?
Điều kiện để delegation đủ: class không được áp thêm invariant nào lên các component thread-safe của nó. Mỗi component phải độc lập, và mỗi method công khai phải hoàn thành công việc chỉ bằng một thao tác đơn lên một component. EventRegistry thỏa: state là đúng một map độc lập, không ràng buộc nào bắt giá trị trong map khớp với biến nào khác.
Thử thêm một tính năng tưởng vô hại: giữ thêm một AtomicInteger count đếm số sự kiện để dashboard đọc cho nhanh, cập nhật nó ngay sau mỗi lần thêm vào map.
Cả events (ConcurrentHashMap) lẫn count (AtomicInteger) đều là component thread-safe, và ta chỉ gọi các thao tác nguyên tử của chúng. Class chứa cả hai có còn thread-safe không? Nếu không, chỗ hở nằm ở đâu? Viết ra phán đoán trước khi đọc tiếp.
public class CountingRegistry { // @NotThreadSafe -- dung lam the nay
private final ConcurrentMap<String, Event> events = new ConcurrentHashMap<>();
private final AtomicInteger count = new AtomicInteger(0);
// INVARIANT: count == events.size()
public void register(Event event) {
if (events.putIfAbsent(event.id(), event) == null) {
count.incrementAndGet(); // cap nhat bien thu hai, o mot buoc rieng
}
}
}
Mỗi component vẫn thread-safe riêng, vậy mà class không còn thread-safe. Ta vừa áp một invariant bắc cầu hai component: count phải luôn bằng events.size(). Giữa putIfAbsent và incrementAndGet tồn tại một cửa sổ — dù vài nano giây — trong đó map đã có entry mới còn count chưa tăng; thread khác đọc đúng vào cửa sổ đó sẽ thấy invariant bị vi phạm. Đây chính là bài học "atomic của một biến không phải atomic của invariant" từ Atomicity, chỉ thay AtomicInteger đơn lẻ bằng các component thread-safe đơn lẻ. Khi delegation hụt hơi thế này, ta phải tự bổ sung đồng bộ cho riêng cụm thao tác đó, thường bằng một khóa của chính class — vẫn là tư duy composition: lắp thêm lớp đồng bộ quanh các mảnh có sẵn, quay về Monitor Pattern cho đúng phần lõi liên đới.

Điều kiện phân định nằm ở nhánh trái so với nhánh phải: bên trái mỗi method là một lời gọi đơn xuống một component độc lập; bên phải, hai component bị buộc bởi một invariant nên có một cửa sổ mà trạng thái nhìn từ ngoài không nhất quán.
Còn một kiểu hụt hơi tinh vi hơn, nằm ở chỗ một thao tác nghiệp vụ vốn dĩ là compound action trên cùng một component — get rồi kiểm tra rồi put — vẫn dính race dù collection thread-safe, và trông y hệt delegation hợp lệ. Kiểu vỡ đó, cùng lời giải bằng compound action nguyên tử của ConcurrentHashMap (putIfAbsent, compute, merge), là trọng tâm của bài 13b — Compound action trên ConcurrentMap.
3. Liên hệ các bài khác
- Atomicity — delegation là câu trả lời "đừng tự viết" cho các race check-then-act mà bài đó mổ xẻ; đây cũng là nơi ranh giới "atomic một biến ≠ atomic invariant" được dựng lên.
- Volatile & synchronized — Monitor Pattern với private lock là phương án ta quay về khi delegation vỡ trước một invariant bắc cầu.
- ReadWriteLock, StampedLock & AQS — khi tự cầm khóa là lựa chọn cuối, các khóa nâng cao ở bài đó là công cụ; delegation là hướng tránh phải tự cầm khóa ngay từ đầu.
- Bài 13b — Compound action trên ConcurrentMap — kiểu delegation vỡ thứ hai (compound action trên một component) và cách vá bằng
compute/merge/putIfAbsentthay client-side locking.
4. Tóm tắt
- Nếu toàn bộ state mutable của một class nằm trong các component đã thread-safe và class không áp invariant nào lên cách chúng phối hợp, thì class tự động thread-safe mà không cần khóa riêng — đó là delegation (
EventRegistryủy thác trọn choConcurrentHashMap). - Điều kiện đủ: mỗi component độc lập, mỗi method công khai hoàn thành việc chỉ bằng một thao tác đơn lên một component.
- Delegation vỡ khi có invariant bắc cầu nhiều component: mỗi field thread-safe riêng nhưng khoảng giữa hai lần cập nhật tạo một cửa sổ mà invariant
count == size()bị vi phạm. - Đây là phiên bản nhiều-component của "atomic một biến không phải atomic của invariant"; khi đó phải tự bổ sung đồng bộ cho cụm liên đới bằng một khóa của chính class (quay về Monitor Pattern cho phần lõi).
- Còn một kiểu vỡ nữa — compound action trên cùng một component — được mổ ở bài 13b.
5. Tự kiểm tra
- Q1Delegation là gì, và vì sao nó ít rủi ro hơn tự cầm một ReentrantLock?
- Q2Điều kiện để delegation là đủ cho thread safety là gì? Cho một ví dụ delegation ĐỦ và một ví dụ delegation VỠ.
- Q3Hai field của một class đều là component thread-safe (ConcurrentHashMap và AtomicInteger). Vì sao class chứa chúng vẫn có thể không thread-safe?
- Q4Khi delegation vỡ trước một invariant bắc cầu, cách sửa là gì? Nó liên hệ thế nào với Monitor Pattern và composition?
- Q5Phân biệt hai kiểu delegation vỡ: invariant bắc cầu nhiều component, và compound action trên một component. Vì sao cả hai đều trông giống delegation hợp lệ?
Bài tiếp theo: Compound action trên ConcurrentMap — compute, merge, putIfAbsent
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
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