Java - 监控器 재entrant
Xin chào các pháp sư Java tương lai! Hôm nay, chúng ta sẽ bắt đầu một hành trình thú vị vào thế giới của các Reentrant Monitors trong Java. Đừng lo lắng nếu bạn mới làm quen với lập trình - tôi sẽ là người hướng dẫn thân thiện của bạn, và chúng ta sẽ cùng nhau từng bước. Nào, cầm lấy pháp杖 ảo của bạn (bàn phím), và chúng ta cùng nhảy vào!
什么是 Reentrant Monitor?
Trước khi chúng ta đi vào chi tiết, hãy hiểu Reentrant Monitor là gì. Hãy tưởng tượng bạn đang ở trong một thư viện ma thuật nơi chỉ một người có thể vào một khu vực cụ thể tại một thời điểm. Bây giờ, nếu bạn đã ở trong khu vực đó và cần đi sâu hơn vào một phân khu vực? Reentrant Monitor giống như một tấm thẻ ma thuật cho phép bạn làm điều đó - vào một khu vực mà bạn đã ở!
Trong thuật ngữ Java, một Reentrant Monitor cho phép một luồng đã giữ một khóa có thể.acquire nó lại mà không bị chặn. Điều này giống như tự cho phép mình vào một căn phòng mà bạn đã ở. Đẹp phải không?
Tại sao chúng ta cần Reentrant Monitors?
Bạn có thể tự hỏi, "Tại sao chúng ta cần tấm thẻ ma thuật này?" Trong thế giới của đa luồng (nơi mà nhiều phần của một chương trình chạy đồng thời), chúng ta thường cần bảo vệ các tài nguyên chia sẻ. Reentrant Monitors giúp chúng ta làm điều này hiệu quả hơn, đặc biệt khi chúng ta có các phương thức gọi các phương thức khác cũng cần cùng một khóa.
Giới thiệu ReentrantLock
Java cung cấp cho chúng ta một lớp gọi là ReentrantLock
để triển khai Reentrant Monitors. Đây giống như tấm thẻ ma thuật của chúng ta, nhưng dưới dạng mã!
Cú pháp
Dưới đây là cách chúng ta tạo và sử dụng một ReentrantLock:
import java.util.concurrent.locks.ReentrantLock;
ReentrantLock lock = new ReentrantLock();
// Để khóa
lock.lock();
try {
// Mã bảo vệ của bạn ở đây
} finally {
// Để解锁
lock.unlock();
}
Đừng lo lắng nếu điều này trông có vẻ rợn rợn. Chúng ta sẽ phân tích nó bằng một số ví dụ!
Đa luồng mà không có Reentrant Lock
Hãy bắt đầu với một ví dụ đơn giản mà không sử dụng ReentrantLock. Hãy tưởng tượng chúng ta có một bộ đếm ma thuật mà nhiều pháp sư (luồng) đang cố gắng tăng giá trị:
public class MagicalCounter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
Bây giờ, hãy tạo một số luồng pháp sư để tăng bộ đếm này:
public class WizardThread extends Thread {
private MagicalCounter counter;
public WizardThread(MagicalCounter counter) {
this.counter = counter;
}
public void run() {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
}
}
public class MagicalCounterTest {
public static void main(String[] args) throws InterruptedException {
MagicalCounter counter = new MagicalCounter();
WizardThread wizard1 = new WizardThread(counter);
WizardThread wizard2 = new WizardThread(counter);
wizard1.start();
wizard2.start();
wizard1.join();
wizard2.join();
System.out.println("Final count: " + counter.getCount());
}
}
Nếu bạn chạy điều này, bạn có thể mong đợi giá trị cuối cùng của bộ đếm là 2000 (1000 lần tăng từ mỗi pháp sư). Nhưng bất ngờ! Kết quả thường nhỏ hơn 2000. Điều này là vì các pháp sư của chúng ta đang bước lên nhau - họ đang cố gắng tăng bộ đếm cùng một lúc, dẫn đến các lần tăng bị mất.
Đa luồng với Reentrant Lock
Bây giờ, hãy rắc một chút phép thuật ReentrantLock vào bộ đếm của chúng ta:
import java.util.concurrent.locks.ReentrantLock;
public class MagicalCounterWithLock {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
Hãy phân tích điều này:
- Chúng ta tạo một đối tượng
ReentrantLock
gọi làlock
. - Trong phương thức
increment
, chúng ta gọilock.lock()
trước khi tăng bộ đếm. - Chúng ta sử dụng khối try-finally để đảm bảo rằng chúng ta luôn解锁, ngay cả khi có ngoại lệ xảy ra.
- Sau khi tăng, chúng ta gọi
lock.unlock()
trong khối finally.
Bây giờ, nếu chúng ta chạy bài kiểm tra WizardThread với bộ đếm mới này, chúng ta sẽ luôn nhận được 2000 là giá trị cuối cùng của bộ đếm. Các pháp sư của chúng ta bây giờ đã rất lịch sự!
Đa luồng với Reentrant Lock là True
ReentrantLock có một chiêu trò khác trong tay. Chúng ta có thể tạo nó với một tham số công bằng:
ReentrantLock fairLock = new ReentrantLock(true);
Khi chúng ta đặt công bằng là true, khóa Ưu tiên cấp quyền truy cập cho luồng chờ đợi lâu nhất. Điều này giống như hình thành một hàng đợi đúng cách cho các pháp sư của chúng ta!
Dưới đây là cách chúng ta có thể sử dụng nó:
public class FairMagicalCounter {
private int count = 0;
private ReentrantLock fairLock = new ReentrantLock(true);
public void increment() {
fairLock.lock();
try {
count++;
} finally {
fairLock.unlock();
}
}
public int getCount() {
return count;
}
}
Điều này đảm bảo rằng nếu nhiều pháp sư đang chờ đợi để tăng bộ đếm, người đã chờ đợi lâu nhất sẽ được đi tiếp theo.
Kết luận
Và thế là bạn đã có, các pháp sư trẻ! Chúng ta đã cùng nhau hành trình qua thế giới ma thuật của Reentrant Monitors trong Java. Chúng ta đã thấy cách chúng giúp chúng ta quản lý các tài nguyên chia sẻ trong môi trường đa luồng, đảm bảo rằng các bộ đếm ma thuật (và các đối tượng chia sẻ khác) được tăng đúng cách.
Nhớ rằng, giống như bất kỳ phép thuật mạnh mẽ nào, Reentrant Monitors nên được sử dụng một cách khôn ngoan. Chúng rất tuyệt vời cho việc quản lý truy cập đồng thời vào các tài nguyên chia sẻ, nhưng việc sử dụng quá mức có thể dẫn đến giảm hiệu suất hoặc thậm chí là deadlock (tình huống mà các pháp sư bị mắc kẹt chờ đợi khóa của nhau mãi mãi!).
Thực hành các范例 mã này, và sớm bạn sẽ có thể ném phép thuật đa luồng như một chuyên gia! Chúc bạn vui vẻ lập trình, và hy vọng rằng các luồng của bạn luôn hòa hợp!
Credits: Image by storyset