| Khía cạnh | CAS | AQS |
|---|---|---|
| Vai trò | Hướng dẫn nguyên tử của CPU / nguyên thủy đồng bộ cấp thấp | Khung cấp cao do Java cung cấp để xây dựng các cơ chế đồng bộ |
| Tư tưởng chính | Khóa lạc quan, không khóa, thực hiện so sánh rồi hoán đổi | Mẫu thiết kế phương thức khuôn mẫu, kết hợp hàng đợi và quản lý trạng thái |
| Cấp độ triển khai | Hướng dẫn phần cứng (mức assembly) | Thư viện Java (dựa trên CAS và cấu trúc hàng đợi) |
| Trọng tâm | Đảm bảo tính nguyên tử cho một biến chia sẻ đơn lẻ | Quản lý thứ tự truy cập tài nguyên chia sẻ giữa nhiều luồng |
| Bài toán giải quyết | Thao tác nguyên tử đơn giản, mức độ chi tiết thấp (ví dụ: i++) | Điều khiển đồng bộ phức tạp, mức độ cao (ví dụ: khóa, semaphore) |
| Đặc điểm hiệu năng | Hiệu suất rất cao khi xung đột ít; tiêu tốn CPU nếu xung đột nhiều do vòng lặp tự quay | Khi xung đột cao, luồng bị đưa vào hàng đợi và treo, tránh lãng phí CPU |
| Mối quan hệ | Là nền tảng để AQS hoạt động — AQS dùng CAS để thao tác state và nút hàng đợi |
Là người dùng cấp cao của CAS — tích hợp CAS, hàng đợi và điều phối luồng |
AQS (AbstractQueuedSynchronizer)
1. AQS là gì?
AQS là lớp trừu tượng nằm trong gói java.util.concurrent, đóng vai trò khung nền để xây dựng các cơ chế đồng bộ như khóa hoặc rào cản. Nó áp dụng mẫu thiết kế Template Method.
2. Nguyên lý hoạt động
AQS duy trì:
- Một biến
volatile int statebiểu diễn trạng thái đồng bộ. - Một hàng đợi hai chiều FIFO (biến thể của CLH queue) để quản lý các luồng đang chờ.
Ý nghĩa của state tùy theo ngữ cảnh:
ReentrantLock: số lần tái nhập của luồng giữ khóa.Semaphore: số lượng giấy phép còn lại.CountDownLatch: giá trị đếm ngược còn lại.
Khi một luồng không lấy được tài nguyên, nó được đóng gói thành Node và thêm vào cuối hàng đợi bằng thao tác CAS. Khi tài nguyên được giải phóng, luồng đầu hàng đợi sẽ được đánh thức để thử lại.
3. Ứng dụng thực tế trong Java
ReentrantLockSemaphoreCountDownLatchReentrantReadWriteLockThreadPoolExecutor.Worker
Các lớp này đều chứa lớp con nội bộ kế thừa từ AQS và ghi đè các phương thức như tryAcquire(), tryRelease() để định nghĩa logic đồng bộ riêng.
4. Cơ chế hoạt động (minh họa qua ReentrantLock)
- Luồng gọi
lock(). - Lớp con của AQS dùng CAS để đổi
statetừ 0 → 1. Nếu thành công, ghi nhận luồng sở hữu. - Nếu thất bại (do
state ≠ 0), luồng được đưa vào hàng đợi và treo. - Khi gọi
unlock(),stateđược đặt lại, và luồng tiếp theo trong hàng đợi được đánh thức.
Toàn bộ quá trình quản lý trạng thái và hàng đợi đều dựa vào CAS để đảm bảo an toàn đa luồng.
CAS (Compare-And-Swap)
1. CAS là gì?
CAS là hướng dẫn nguyên tử của CPU, được Java phơi bày qua lớp sun.misc.Unsafe hoặc các lớp trong gói java.util.concurrent.atomic.
2. Nguyên lý
CAS gồm ba tham số: địa chỉ bộ nhớ (V), giá trị kỳ vọng (A), giá trị mới (B):
- Nếu giá trị tại
VbằngA, ghiBvàoVvà trả vềtrue. - Ngược lại, trả về
false(không thay đổi).
boolean compareAndSwap(int* V, int A, int B) {
if (*V == A) {
*V = B;
return true;
}
return false;
}
3. Ứng dụng trong Java
Các lớp như AtomicInteger, AtomicReference sử dụng CAS ở底层:
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // thread-safe nhờ CAS
boolean updated = counter.compareAndSet(1, 2); // đổi 1 → 2 nếu hiện tại là 1
4. Ưu và nhược điểm
Ưu điểm: Không cần khóa, tránh chi phí chuyển ngữ cảnh luồng, hiệu suất cao khi xung đột thấp.
Nhược điểm:
- Vấn đề ABA: Giá trị A → B → A khiến CAS tưởng không có thay đổi. Giải pháp: dùng
AtomicStampedReference. - Tự quay vô hạn: Khi xung đột cao, CAS thất bại liên tục → tiêu tốn CPU.
- Chỉ áp dụng cho một biến: Không hỗ trợ nguyên tử trên nhiều biến cùng lúc.
Ví dụ minh họa: Giả sử value = 5.
- Luồng A đọc
old = 5, tínhnew = 6. - Trước khi A thực hiện CAS, luồng B đã tăng
valuelên 6. - A thực hiện CAS(5, 6): thất bại vì giá trị hiện tại là 6 ≠ 5.
- A lặp lại: đọc lại
old = 6, tínhnew = 7, thử CAS(6, 7) — thành công nếu không có can thiệp.
Đoạn mã minh họa từ AtomicInteger
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next)) {
return current;
}
// Thất bại → lặp lại
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Cách giảm thiểu tự quay vô hạn trong CAS
- Lùi dần theo hàm mũ (Exponential Backoff):
int attempts = 0; while (!attemptCAS()) { attempts++; if (attempts > MAX_RETRY) { fallbackToBlockingLock(); break; } Thread.sleep(Math.min(100, 1 << attempts)); } - Thêm ngẫu nhiên vào thời gian chờ:
while (!attemptCAS()) { Thread.sleep(ThreadLocalRandom.current().nextInt(10, 100)); } - Chiến lược lai: Tự quay vài lần →
Thread.yield()→ chuyển sang hàng đợi treo (như AQS). - Giới hạn số lần tự quay:
for (int i = 0; i < MAX_SPINS; i++) { if (attemptCAS()) return true; } // Chuyển sang cơ chế chặn
Xử lý thực tế trong Java
Các lớp AtomicInteger không tích hợp cơ chế chống tự quay vô hạn. Chúng dựa vào:
- Tính công bằng của bộ lập lịch luồng JVM/OS.
- Giả định rằng xung đột cực đoan hiếm xảy ra.
- Khuyến nghị chuyển sang
synchronizedhoặcReentrantLocknếu xung đột cao.
Do đó, AQS chọn cách treo luồng thay vì tự quay — đánh đổi hiệu suất tức thì để đạt tính công bằng và ổn định lâu dài.
| Phương thức | Có tự quay? | Mục đích | Kiểu trả về |
|---|---|---|---|
compareAndSet(expect, update) |
Không | Thử CAS một lần | boolean |
getAndIncrement() |
Có | Tăng nguyên tử, trả về giá trị cũ | int |
incrementAndGet() |
Có | Tăng nguyên tử, trả về giá trị mới | int |
getAndAdd(delta) |
Có | Cộng delta nguyên tử, trả về giá trị cũ | int |
weakCompareAndSet() |
Không | CAS với ngữ nghĩa bộ nhớ yếu | boolean |