Jelas, notify
bangun (ada) satu utas di set tunggu, notifyAll
bangun semua utas di set tunggu. Diskusi berikut harus menjernihkan keraguan. notifyAll
harus digunakan sebagian besar waktu. Jika Anda tidak yakin mana yang akan digunakan, maka gunakan. notifyAll
Silakan lihat penjelasan yang berikut.
Baca dengan sangat hati-hati dan mengerti. Silakan kirim saya email jika Anda memiliki pertanyaan.
Lihatlah produsen / konsumen (asumsi adalah kelas ProduserConsumer dengan dua metode). ITU RUSAK (karena menggunakan notify
) - ya itu MUNGKIN bekerja - bahkan sebagian besar waktu, tetapi juga dapat menyebabkan kebuntuan - kita akan melihat mengapa:
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;
}
PERTAMA,
Mengapa kita perlu loop sementara di sekitar menunggu?
Kami membutuhkan while
loop jika kami mendapatkan situasi ini:
Konsumen 1 (C1) memasuki blok yang disinkronkan dan buffer kosong, sehingga C1 dimasukkan ke dalam set tunggu (melalui wait
panggilan). Konsumen 2 (C2) akan memasuki metode yang disinkronkan (pada titik Y di atas), tetapi Produser P1 menempatkan objek di buffer, dan kemudian memanggil notify
. Satu-satunya utas menunggu adalah C1, sehingga dibangunkan dan sekarang mencoba untuk mendapatkan kembali kunci objek pada titik X (di atas).
Sekarang C1 dan C2 berusaha mendapatkan kunci sinkronisasi. Salah satunya (tanpa syarat) dipilih dan memasuki metode, yang lain diblokir (tidak menunggu - tetapi diblokir, mencoba untuk mendapatkan kunci pada metode). Katakanlah C2 mendapatkan kunci terlebih dahulu. C1 masih memblokir (berusaha mendapatkan kunci di X). C2 menyelesaikan metode dan melepaskan kunci. Sekarang, C1 mendapatkan kunci. Coba tebak, beruntung kita memiliki while
loop, karena, C1 melakukan loop check (guard) dan dicegah untuk menghapus elemen yang tidak ada dari buffer (C2 sudah mendapatkannya!). Jika kita tidak memiliki while
, kita akan mendapatkan IndexArrayOutOfBoundsException
ketika C1 mencoba untuk menghapus elemen pertama dari buffer!
SEKARANG,
Oke, sekarang mengapa kita perlu memberi tahu SEMUA?
Dalam contoh produsen / konsumen di atas, sepertinya kita bisa lolos notify
. Kelihatannya seperti ini, karena kita dapat membuktikan bahwa penjaga di loop menunggu untuk produsen dan konsumen saling eksklusif. Artinya, sepertinya kita tidak dapat memiliki utas menunggu dalam put
metode maupun get
metode, karena, agar itu benar, maka yang berikut ini harus benar:
buf.size() == 0 AND buf.size() == MAX_SIZE
(anggap MAX_SIZE bukan 0)
NAMUN, ini tidak cukup baik, kami PERLU untuk menggunakannya notifyAll
. Mari kita lihat mengapa ...
Asumsikan kita memiliki buffer ukuran 1 (untuk membuat contoh mudah diikuti). Langkah-langkah berikut membawa kita ke jalan buntu. Perhatikan bahwa KAPAN SAJA utas dibangunkan dengan memberi tahu, utas dapat dipilih secara non-deterministik oleh JVM - yaitu utas tunggu mana pun dapat dibangunkan. Perhatikan juga bahwa ketika banyak utas menghalangi saat masuk ke metode (yaitu mencoba memperoleh kunci), urutan akuisisi dapat menjadi non-deterministik. Ingat juga bahwa utas hanya dapat berada dalam salah satu metode pada satu waktu - metode yang disinkronkan hanya memungkinkan satu utas untuk mengeksekusi (yaitu memegang kunci) metode apa saja (disinkronkan) di kelas. Jika urutan peristiwa berikut terjadi - hasil kebuntuan:
LANGKAH 1:
- P1 menempatkan 1 char ke buffer
LANGKAH 2:
- Upaya P2 put
- memeriksa lingkaran tunggu - sudah menjadi tanda tunggu
LANGKAH 3:
- Upaya P3 put
- memeriksa lingkaran tunggu - sudah menjadi tanda tunggu
LANGKAH 4:
- C1 berupaya mendapatkan 1 karakter
- C2 mencoba mendapatkan 1 karakter - blok saat masuk ke get
metode
- C3 berupaya mendapatkan 1 karakter - blok saat masuk ke get
metode
LANGKAH 5:
- C1 mengeksekusi get
metode - mendapatkan char, panggilan notify
, metode keluar
- notify
Bangun P2
- TETAPI, C2 memasukkan metode sebelum P2 dapat (P2 harus mendapatkan kembali kunci), jadi blok P2 pada saat masuk ke put
metode
- C2 memeriksa loop menunggu, tidak ada lagi karakter dalam buffer, jadi tunggu
- C3 memasuki metode setelah C2, tetapi sebelum P2, memeriksa loop menunggu, tidak ada lagi karakter dalam buffer, jadi tunggu
LANGKAH 6:
- SEKARANG: ada P3, C2, dan C3 menunggu!
- Akhirnya P2 mendapatkan kunci, menempatkan char di buffer, panggilan notify, keluar dari metode
LANGKAH 7:
- Pemberitahuan P2 membangunkan P3 (ingat utas apa pun dapat dibangunkan)
- P3 memeriksa kondisi loop menunggu, sudah ada arang di buffer, jadi tunggu.
- TIDAK ADA LEBIH BANYAK BENANG UNTUK PEMBERITAHUAN dan TIGA THREAD YANG DITANGGUHKAN!
SOLUSI: Ganti notify
dengan notifyAll
kode produsen / konsumen (di atas).