A wait()
hanya masuk akal ketika ada juga notify()
, jadi selalu tentang komunikasi antara utas, dan yang membutuhkan sinkronisasi agar berfungsi dengan benar. Orang dapat berargumen bahwa ini harusnya implisit, tetapi itu tidak akan benar-benar membantu, karena alasan berikut:
Secara semantik, Anda tidak pernah adil wait()
. Anda perlu beberapa kondisi untuk dipuaskan, dan jika tidak, Anda menunggu sampai kondisi itu dipenuhi. Jadi apa yang sebenarnya Anda lakukan adalah
if(!condition){
wait();
}
Tetapi kondisinya sedang diatur oleh utas terpisah, jadi untuk menjalankannya dengan benar, Anda perlu sinkronisasi.
Beberapa hal lagi yang salah dengan itu, di mana hanya karena utas Anda berhenti menunggu bukan berarti kondisi yang Anda cari benar:
Anda bisa mendapatkan bangun palsu (artinya utas dapat bangun dari menunggu tanpa pernah menerima pemberitahuan), atau
Kondisi dapat diatur, tetapi utas ketiga membuat kondisi salah lagi pada saat utas menunggu bangun (dan meminta kembali monitor).
Untuk menangani kasus-kasus ini apa yang benar-benar Anda butuhkan adalah selalu beberapa variasi dari ini:
synchronized(lock){
while(!condition){
lock.wait();
}
}
Lebih baik lagi, jangan main-main dengan sinkronisasi primitif sama sekali dan bekerja dengan abstraksi yang ditawarkan dalam java.util.concurrent
paket.
Thread.interrupted()
.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
Inilah yang berpotensi terjadi:
Seorang konsumen menelepon
take()
dan melihat bahwabuffer.isEmpty()
.Sebelum utas konsumen meneruskan ke panggilan
wait()
, utas produsen datang dan memanggil penuhgive()
, yaitu,buffer.add(data); notify();
Benang konsumen sekarang akan memanggil
wait()
(dan kehilangan yangnotify()
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 memastikannotify
tidak pernah dipanggil antaraisEmpty
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:
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 dalamsynchronized
blok.Posting ini telah ditulis ulang sebagai artikel di sini: Java: Mengapa menunggu harus dipanggil dalam blok yang disinkronkan
sumber
return buffer.remove();
blok sementara tetapi setelahwait();
, itu berhasil?wait
kembali.Thread.currentThread().wait();
dimain
fungsi dikelilingi oleh try-catch untukInterruptedException
. Tanpasynchronized
blok, itu memberi saya pengecualian yang samaIllegalMonitorStateException
. Apa yang membuatnya mencapai keadaan ilegal sekarang? Ini bekerja di dalamsynchronized
blok.@ Ballollall benar. The
wait()
disebut, sehingga benang dapat menunggu untuk beberapa kondisi terjadi ketika hal iniwait()
panggilan terjadi, benang dipaksa menyerah kuncinya.Untuk melepaskan sesuatu, Anda harus memilikinya terlebih dahulu. Utas perlu memiliki kunci terlebih dahulu. Oleh karena itu kebutuhan untuk memanggilnya di dalam
synchronized
metode / blok.Ya, saya setuju dengan semua jawaban di atas mengenai potensi kerusakan / inkonsistensi jika Anda tidak memeriksa kondisi dalam
synchronized
metode / blok. Namun seperti yang ditunjukkan @ shrini1000, hanya dengan memanggilwait()
blok yang disinkronkan tidak akan mencegah terjadinya ketidakkonsistenan ini.Ini bacaan yang bagus ..
sumber
Masalah yang mungkin ditimbulkannya jika Anda tidak melakukan sinkronisasi sebelumnya
wait()
adalah sebagai berikut:makeChangeOnX()
dan cek kondisi sementara, dan itutrue
(x.metCondition()
pengembalianfalse
, berartix.condition
adalahfalse
) sehingga akan mendapatkan di dalamnya. Kemudian tepat sebelumwait()
metode, utas lain pergi kesetConditionToTrue()
dan mengaturx.condition
ketrue
dannotifyAll()
.wait()
metodenya (tidak terpengaruh olehnotifyAll()
yang terjadi beberapa saat sebelumnya). Dalam hal ini, utas pertama akan tetap menunggu utas lainnya tampilsetConditionToTrue()
, tetapi itu mungkin tidak terjadi lagi.sumber
Kita semua tahu bahwa metode wait (), notify () dan notifyAll () digunakan untuk komunikasi antar-thread. Untuk menghilangkan sinyal yang tidak terjawab dan masalah bangun palsu, thread menunggu selalu menunggu pada beberapa kondisi. misalnya-
Kemudian memberitahukan set thread adalah Variabel terverifikasi ke true dan memberitahukan.
Setiap utas memiliki cache lokal mereka sehingga semua perubahan pertama kali ditulis di sana dan kemudian dipromosikan ke memori utama secara bertahap.
Seandainya metode ini tidak dipanggil dalam blok yang disinkronkan, variabel wasNotified tidak akan masuk ke memori utama dan akan ada di cache lokal thread sehingga thread yang menunggu akan tetap menunggu sinyal meskipun sudah diatur ulang dengan memberi tahu thread.
Untuk memperbaiki masalah jenis ini, metode ini selalu dipanggil di dalam blok yang disinkronkan yang memastikan bahwa ketika blok yang disinkronkan dimulai maka semuanya akan dibaca dari memori utama dan akan dimasukkan ke dalam memori utama sebelum keluar dari blok yang disinkronkan.
Terima kasih, harap ini menjelaskan.
sumber
Ini pada dasarnya ada hubungannya dengan arsitektur perangkat keras (yaitu RAM dan cache ).
Jika Anda tidak menggunakannya
synchronized
bersama-sama denganwait()
ataunotify()
, utas lain dapat memasuki blok yang sama alih-alih menunggu monitor untuk memasukkannya. Selain itu, ketika misalnya mengakses array tanpa blok yang disinkronkan, utas lain mungkin tidak melihat perubahannya ... sebenarnya utas lain tidak akan melihat perubahan apa pun ketika sudah memiliki salinan array dalam cache tingkat-x ( alias cache level 1/2/3) dari thread yang menangani inti CPU.Tetapi blok yang disinkronkan hanya satu sisi dari medali: Jika Anda benar-benar mengakses suatu objek dalam konteks yang disinkronkan dari konteks yang tidak disinkronkan, objek itu masih tidak akan disinkronkan bahkan di dalam blok yang disinkronkan, karena itu memegang salinan sendiri dari objek dalam cache-nya. Saya menulis tentang masalah ini di sini: https://stackoverflow.com/a/21462631 dan Ketika kunci memegang objek non-final, dapatkah referensi objek masih diubah oleh utas lain?
Selain itu, saya yakin bahwa cache tingkat-x bertanggung jawab atas sebagian besar kesalahan runtime yang tidak dapat direproduksi. Itu karena pengembang biasanya tidak mempelajari hal-hal tingkat rendah, seperti bagaimana CPU bekerja atau bagaimana hirarki memori mempengaruhi jalannya aplikasi: http://en.wikipedia.org/wiki/Memory_hierarchy
Tetap menjadi teka-teki mengapa kelas pemrograman tidak dimulai dengan hierarki memori dan arsitektur CPU terlebih dahulu. "Halo dunia" tidak akan membantu di sini. ;)
sumber
langsung dari ini java oracle tutorial:
sumber
Saat Anda memanggil notify () dari objek t, java memberi tahu metode t.wait () tertentu. Tapi, bagaimana pencarian java dan memberitahukan metode menunggu tertentu.
java hanya melihat ke blok kode yang disinkronkan yang dikunci oleh objek t. java tidak dapat mencari seluruh kode untuk memberi tahu t.wait () tertentu.
sumber
sesuai dokumen:
wait()
Metode hanya berarti melepaskan kunci pada objek. Jadi objek hanya akan dikunci dalam blok / metode yang disinkronkan. Jika utas di luar blok sinkronisasi berarti tidak dikunci, jika tidak dikunci maka apa yang akan Anda lepaskan pada objek?sumber
Utas menunggu pada objek pemantauan (objek yang digunakan oleh blok sinkronisasi), Dapat ada n jumlah objek pemantauan dalam seluruh perjalanan satu utas. Jika Thread menunggu di luar blok sinkronisasi maka tidak ada objek pemantauan dan juga utas lainnya memberi tahu untuk mengakses objek pemantauan, jadi bagaimana utas di luar blok sinkronisasi akan tahu bahwa ia telah diberitahu. Ini juga salah satu alasan bahwa wait (), notify () dan notifyAll () berada di kelas objek daripada kelas thread.
Pada dasarnya objek pemantauan adalah sumber umum di sini untuk semua utas, dan objek pemantauan hanya dapat tersedia di blok sinkronisasi.
sumber