Rõ ràng, notify
đánh thức (bất kỳ) một luồng trong tập chờ, notifyAll
đánh thức tất cả các luồng trong tập chờ. Các cuộc thảo luận sau đây sẽ làm sáng tỏ bất kỳ nghi ngờ. notifyAll
nên được sử dụng hầu hết thời gian. Nếu bạn không chắc chắn nên sử dụng cái nào, thì hãy sử dụng. notifyAll
Vui lòng xem giải thích sau.
Đọc rất kỹ và hiểu. Xin vui lòng gửi cho tôi một email nếu bạn có bất kỳ câu hỏi.
Nhìn vào nhà sản xuất / người tiêu dùng (giả định là một lớp ProducerConsumer với hai phương thức). NÓ LÀ MÔI GIỚI (vì nó sử dụng notify
) - vâng, nó CÓ THỂ hoạt động - thậm chí hầu hết thời gian, nhưng nó cũng có thể gây ra bế tắc - chúng ta sẽ thấy tại sao:
public synchronized void put(Object o) {
while (buf.size()==MAX_SIZE) {
wait(); // called if the buffer is full (try/catch removed for brevity)
}
buf.add(o);
notify(); // called in case there are any getters or putters waiting
}
public synchronized Object get() {
// Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
while (buf.size()==0) {
wait(); // called if the buffer is empty (try/catch removed for brevity)
// X: this is where C1 tries to re-acquire the lock (see below)
}
Object o = buf.remove(0);
notify(); // called if there are any getters or putters waiting
return o;
}
ĐẦU TIÊN
Tại sao chúng ta cần một vòng lặp trong khi chờ đợi?
Chúng ta cần một while
vòng lặp trong trường hợp chúng ta gặp tình huống này:
Người tiêu dùng 1 (C1) nhập khối được đồng bộ hóa và bộ đệm trống, do đó C1 được đặt trong bộ chờ (thông qua wait
cuộc gọi). Người tiêu dùng 2 (C2) sắp nhập phương thức được đồng bộ hóa (tại điểm Y ở trên), nhưng Nhà sản xuất P1 đặt một đối tượng vào bộ đệm và sau đó gọi notify
. Chuỗi chờ duy nhất là C1, vì vậy nó được đánh thức và bây giờ cố gắng lấy lại khóa đối tượng tại điểm X (ở trên).
Bây giờ C1 và C2 đang cố gắng để có được khóa đồng bộ hóa. Một trong số chúng (không đặc biệt) được chọn và nhập vào phương thức, cái còn lại bị chặn (không chờ đợi - nhưng bị chặn, cố lấy khóa trên phương thức). Giả sử C2 lấy khóa trước. C1 vẫn đang chặn (cố gắng lấy khóa tại X). C2 hoàn thành phương pháp và giải phóng khóa. Bây giờ, C1 mua lại khóa. Đoán xem, may mắn là chúng ta có một while
vòng lặp, bởi vì, C1 thực hiện kiểm tra vòng lặp (bảo vệ) và được ngăn chặn loại bỏ một phần tử không tồn tại khỏi bộ đệm (C2 đã có nó!). Nếu chúng ta không có while
, chúng ta sẽ nhận được một IndexArrayOutOfBoundsException
C1 cố gắng loại bỏ phần tử đầu tiên khỏi bộ đệm!
HIỆN NAY,
Ok, bây giờ tại sao chúng ta cần thông báo?
Trong ví dụ về nhà sản xuất / người tiêu dùng ở trên, có vẻ như chúng ta có thể thoát khỏi notify
. Có vẻ như vậy, bởi vì chúng tôi có thể chứng minh rằng những người bảo vệ trên các vòng chờ cho nhà sản xuất và người tiêu dùng là loại trừ lẫn nhau. Đó là, có vẻ như chúng ta không thể có một luồng chờ trong put
phương thức cũng như get
phương thức, bởi vì, để điều đó là đúng, thì điều sau đây sẽ phải là đúng:
buf.size() == 0 AND buf.size() == MAX_SIZE
(giả sử MAX_SIZE không phải là 0)
TUY NHIÊN, điều này là không đủ tốt, chúng tôi CẦN sử dụng notifyAll
. Hãy xem tại sao ...
Giả sử chúng ta có một bộ đệm có kích thước 1 (để làm cho ví dụ dễ theo dõi). Các bước sau đây dẫn chúng ta đến bế tắc. Lưu ý rằng BẤT K a một luồng nào được thông báo bằng thông báo, nó có thể không được xác định bởi JVM - đó là bất kỳ luồng chờ nào có thể được đánh thức. Cũng lưu ý rằng khi nhiều luồng đang chặn khi nhập vào một phương thức (nghĩa là cố gắng thu được khóa), thứ tự thu nhận có thể không xác định. Cũng cần nhớ rằng một luồng chỉ có thể ở một trong các phương thức tại một thời điểm - các phương thức được đồng bộ hóa chỉ cho phép một luồng được thực thi (tức là giữ khóa) bất kỳ phương thức (được đồng bộ hóa nào) trong lớp. Nếu chuỗi sự kiện sau xảy ra - kết quả bế tắc:
BƯỚC 1:
- P1 đặt 1 char vào bộ đệm
BƯỚC 2:
- P2 lần thử put
- kiểm tra vòng chờ - đã là char - chờ
BƯỚC 3:
- Nỗ lực P3 put
- kiểm tra vòng chờ - đã là char - chờ
BƯỚC 4:
- C1 cố gắng để có được 1 char
- C2 cố gắng để có được 1 khối char khi vào get
phương thức
- C3 cố gắng để có được 1 khối char khi vào get
phương thức
BƯỚC 5:
- C1 đang thực thi get
phương thức - lấy phương thức char, gọi notify
, thoát
- Phương notify
thức đánh thức P2
- BUT, C2 nhập trước khi P2 có thể (P2 phải truy vấn khóa), vì vậy P2 chặn vào mục nhập put
phương thức
- C2 kiểm tra vòng chờ, không còn ký tự trong bộ đệm, vì vậy hãy chờ
- C3 nhập phương thức sau C2, nhưng trước P2, kiểm tra vòng chờ, không còn ký tự trong bộ đệm, vì vậy hãy đợi
BƯỚC 6:
- NGAY BÂY GIỜ: có P3, C2 và C3 đang chờ!
- Cuối cùng P2 có được khóa, đặt char vào bộ đệm, thông báo cuộc gọi, thoát phương thức
BƯỚC 7:
- Thông báo của P2 đánh thức P3 (hãy nhớ bất kỳ luồng nào có thể được đánh thức)
- P3 kiểm tra điều kiện vòng chờ, đã có char trong bộ đệm, vì vậy hãy chờ.
- KHÔNG CÓ THÊM NHIỀU THÊM ĐỂ GỌI THÔNG BÁO VÀ BA BA MỌI THỜI GIAN TUYỆT VỜI!
GIẢI PHÁP: Thay thế notify
bằng notifyAll
mã nhà sản xuất / người tiêu dùng (ở trên).