Apa potensi kerusakan jika dimungkinkan untuk menjalankan di wait()
luar blok yang disinkronkan, mempertahankan semantiknya - menangguhkan utas penelepon?
Mari kita ilustrasikan masalah apa yang akan kita hadapi jika wait()
bisa disebut di luar blok yang disinkronkan dengan contoh nyata .
Misalkan kita harus mengimplementasikan antrian pemblokiran (saya tahu, sudah ada satu di API :)
Upaya pertama (tanpa sinkronisasi) dapat melihat sesuatu di sepanjang garis di bawah ini
class BlockingQueue {
Queue<String> buffer = new LinkedList<String>();
public void give(String data) {
buffer.add(data);
notify(); // Since someone may be waiting in take!
}
public String take() throws InterruptedException {
while (buffer.isEmpty()) // don't use "if" due to spurious wakeups.
wait();
return buffer.remove();
}
}
Inilah yang berpotensi terjadi:
Seorang konsumen menelepon take()
dan melihat bahwa buffer.isEmpty()
.
Sebelum utas konsumen meneruskan ke panggilan wait()
, utas produsen datang dan memanggil penuh give()
, yaitu,buffer.add(data); notify();
Benang konsumen sekarang akan memanggil wait()
(dan kehilangan yang notify()
yang hanya disebut).
Jika sial, utas produsen tidak akan menghasilkan lebih banyak give()
karena fakta bahwa utas konsumen tidak pernah bangun, dan kami memiliki dead-lock.
Setelah Anda memahami masalahnya, solusinya sudah jelas: Gunakan synchronized
untuk memastikan notify
tidak pernah dipanggil antara isEmpty
danwait
.
Tanpa merinci: Masalah sinkronisasi ini bersifat universal. Seperti yang ditunjukkan oleh Michael Borgwardt, tunggu / beri tahu tentang komunikasi antar utas, sehingga Anda akan selalu berakhir dengan kondisi balapan yang serupa dengan yang dijelaskan di atas. Inilah sebabnya mengapa aturan "hanya tunggu di dalam sinkronisasi" diberlakukan.
Sebuah paragraf dari tautan yang diposting oleh @ Willie merangkumnya dengan cukup baik:
Anda memerlukan jaminan mutlak bahwa pelayan dan pemberi tahu setuju tentang keadaan predikat. Pelayan memeriksa keadaan predikat pada titik tertentu sedikit SEBELUM itu tertidur, tetapi tergantung pada kebenaran pada predikat yang benar KETIKA tidur. Ada periode kerentanan antara kedua peristiwa itu, yang dapat merusak program.
Predikat yang harus disetujui oleh produsen dan konsumen ada dalam contoh di atas buffer.isEmpty()
. Dan perjanjian diselesaikan dengan memastikan bahwa menunggu dan memberitahukan dilakukan dalam synchronized
blok.
Posting ini telah ditulis ulang sebagai artikel di sini: Java: Mengapa menunggu harus dipanggil dalam blok yang disinkronkan