Quản lý đa luồng và xử lý Thread Safety với Interface Lock trong Java

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ặc lockInterruptibly(). 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 đặt unlock() trong khối finally.

Thẻ: Java multithreading Concurrency ReentrantLock ThreadSafety

Đăng vào ngày 22 tháng 6 lúc 07:51