Cơ chế đồng bộ luồng trong Java là nền tảng cốt lõi cho phát triển ứng dụng đa luồng. Dưới đây là phân tích toàn diện về các khía cạnh kỹ thuật quan trọng:
- Khái niệm nền tảng về đồng bộ
Khi nhiều luồng truy cập tài nguyên chia sẻ mà không kiểm soát, sẽ xảy ra trạng thái race condition và vi phạm tính nguyên tử. Ví dụ: hai luồng cùng đọc số dư tài khoản 100, luồng A nạp thêm 50 (tổng 150), luồng B rút 30 (tổng 70) thay vì kết quả đúng là 120.
- Cơ chế khóa ẩn (synchronized)
// Khóa đối tượng cụ thể
public void method() {
synchronized (obj) {
balance += amount;
}
}
// Khóa this (đối tượng hiện tại)
synchronized (this) {
// Truy cập biến instance
}
// Khóa class (đối tượng tĩnh)
synchronized (MyClass.class) {
// Truy cập biến static
}
Nguyên lý hoạt động:
- Sử dụng Mark Word trong header object để quản lý trạng thái khóa
- Quá trình tiến hóa: không khóa → khóa thiên vị → khóa nhẹ → khóa nặng
- Đặc tính: độc quyền, có thể tái nhập, không thể gián đoạn
- Cơ chế khóa hiển thị (Lock)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
Tính năng nâng cao:
- Khóa có thể gián đoạn (
lockInterruptibly()) - Thử khóa trong thời gian giới hạn (
tryLock(timeout)) - Khóa đọc-viết (
ReadWriteLock)
- Biến nguyên tử
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
public boolean compareAndSet(int expected, int value) {
return count.compareAndSet(expected, value);
}
}
Nguyên lý CAS:
- So sánh giá trị hiện tại với giá trị kỳ vọng
- Cập nhật nếu khớp, không thực hiện gì nếu không khớp
- Biến cục bộ luồng (ThreadLocal)
public class SessionManager {
private static ThreadLocal<String> userContext = new ThreadLocal<>();
public void setUser(String user) {
userContext.set(user);
}
public String getUser() {
return userContext.get();
}
public void clear() {
userContext.remove();
}
}
- So sánh các cơ chế đồng bộ
| Đặc điểm | synchronized | ReentrantLock | Atomic | ThreadLocal |
|---|---|---|---|---|
| Loại khóa | Ẩn | Hiển | Không khóa (CAS) | Không khóa (Isolation) |
| Tính linh hoạt | Thấp | Cao (có thể thử, gián đoạn, timeout) | Cao | Cao |
| Hiệu năng | Tốt sau tối ưu | Tốt | Tốt nhất | Tốt nhất |
| Tính tái nhập | Có | Có | Không áp dụng | Không áp dụng |
- Chủ đề nâng cao và thực tiễn
Vấn đề khóa chết:
// Luồng 1 giữ lockA đợi lockB
synchronized (lockA) {
synchronized (lockB) { /* ... */ }
}
// Luồng 2 giữ lockB đợi lockA
synchronized (lockB) {
synchronized (lockA) { /* ... */ }
}
Chiến lược phòng tránh:
- Sắp xếp thứ tự khóa cố định
- Sử dụng
tryLock()có timeout - Giảm độ mịn khóa (lock granular)
- Ưu tiên tập hợp đồng bộ sẵn có
Bẫy thường gặp:
| Bẫy | Triệu chứng | Giải pháp |
|---|---|---|
| Khóa chết | Luồng bị treo vĩnh viễn | Thứ tự khóa thống nhất, sử dụng cơ chế thử khóa |
| Khóa sống | Luồng liên tục retry nhưng thất bại | Thêm backoff ngẫu nhiên, điều chỉnh chiến lược retry |
| Thiếu tài nguyên | Một số luồng không nhận được tài nguyên | Khóa công bằng, điều chỉnh độ ưu tiên |
- Lời khuyên thiết kế hệ thống
- Hiệu năng cao: Ưu tiên biến nguyên tử và khóa đọc-viết
- Độ phức tạp: Dùng synchronized cho đơn giản, Lock cho phức tạp
- Dễ bảo trì: Ghi nhớ giải phóng khóa trong khối finally
- Mở rộng: Chọn giải pháp có khả năng mở rộng tốt
Trong hệ thống phân tán, cần kết hợp với khóa phân tán (Redis, ZooKeeper) và giao dịch phân tán (TCC, Seata). Điều này đòi hỏi kiến trúc sư phải cân bằng giữa tính nhất quán và hiệu năng hệ thống.