Trong lập trình đa luồng Java, việc kiểm soát truy cập vào tài nguyên dùng chung là cực kỳ quan trọng để tránh tình trạng xung đột dữ liệu (race condition). Interface Lock, được giới thiệu từ phiên bản JDK 1.5, cung cấp các cơ chế đồng bộ hóa linh hoạt và mạnh mẽ hơn so với từ khóa synchronized truyền thống.
Giới thiệu về Interface Lock
Lock không bị giới hạn trong các khối code (block-structured). Nó cho phép lập trình viên kiểm soát việc thu hồi và giải phóng khóa một cách tường minh. Một trong những triển khai phổ biến nhất của interface này là ReentrantLock, hỗ trợ các tính năng nâng cao như khóa công bằng (fairness policy) hoặc khả năng ngắt quãng luồng đang chờ khóa.
Triển khai bài toán bán vé an toàn
Dưới đây là ví dụ về việc sử dụng ReentrantLock để giải quyết bài toán nhiều quầy cùng bán một lượng vé giới hạn mà không gây ra sai lệch dữ liệu.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketCounter implements Runnable {
// Tài nguyên dùng chung
private int stock = 100;
// Khởi tạo đối tượng Lock
private final Lock ticketLock = new ReentrantLock();
@Override
public void run() {
while (true) {
// Thực hiện khóa trước khi truy cập tài nguyên dùng chung
ticketLock.lock();
try {
if (stock > 0) {
// Mô phỏng thời gian xử lý nghiệp vụ
Thread.sleep(50);
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " đã bán vé số: " + stock);
stock--;
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Luôn giải phóng khóa trong khối finally để tránh deadlock
ticketLock.unlock();
}
}
}
}
Tiếp theo là lớp thực thi để khởi tạo các luồng mô phỏng các quầy bán vé khác nhau:
public class CinemaSystem {
public static void main(String[] args) {
TicketCounter task = new TicketCounter();
// Tạo 3 luồng đại diện cho 3 quầy bán vé
Thread pos1 = new Thread(task, "Quầy A");
Thread pos2 = new Thread(task, "Quầy B");
Thread pos3 = new Thread(task, "Quầy C");
pos1.start();
pos2.start();
pos3.start();
}
}
So sánh giữa synchronized và Lock
1. Cơ chế Synchronized
- Ưu điểm: Tự động giải phóng khóa khi luồng kết thúc hoặc xảy ra ngoại lệ, cú pháp đơn giản, không yêu cầu quản lý thủ công.
- Nhược điểm: Thiếu linh hoạt, không thể thử lấy khóa (tryLock) mà không chờ đợi, và bắt buộc phải giải phóng khóa theo thứ tự ngược lại với lúc lấy khóa trong cùng một khối lệnh.
2. Cơ chế Lock
- Ưu điểm: Cung cấp nhiều phương thức mở rộng như
tryLock()(giúp tránh tình trạng chờ đợi vô hạn) hoặclockInterruptibly(). Cấu trúc không bắt buộc phải nằm trong một khối lệnh duy nhất, cho phép lấy khóa ở phương thức này và giải phóng ở phương thức khác. - Nhược điểm: Yêu cầu lập trình viên phải tự quản lý việc giải phóng khóa. Nếu quên gọi
unlock(), hệ thống sẽ rơi vào tình trạng treo (deadlock). Do đó, quy tắc bắt buộc là phải đặtunlock()trong khốifinally.