Tujuan komunikasi utas adalah untuk memungkinkan utas untuk mengirim sinyal satu sama lain. Di sisi lain, komunikasi utas memungkinkan utas untuk menunggu sinyal dari utas lain.
Komunikasi melalui objek bersama
Cara mudah untuk mengirim sinyal antar utas adalah dengan mengatur nilai sinyal dalam variabel objek bersama. Thread A menetapkan variabel anggota boolean hasDataToprocess ke true di blok sinkronisasi, dan Thread B juga membaca variabel anggota HasDataToprocess di blok sinkronisasi. Contoh sederhana ini menggunakan objek yang memegang sinyal dan menyediakan metode set dan periksa:
kelas publik mySignal {hasdatatoprocess boolean yang dilindungi = false; hasdatatoproses boolean yang disinkronkan publik () {return this.hasdataToprocess; } public yang disinkronkan void sethasdataToprocess (boolean hasdata) {this.hasDataToProcess = hasdata; }}Threads A dan B harus mendapatkan referensi ke contoh bersama MySignal untuk komunikasi. Jika referensi yang mereka miliki menunjukkan berbagai contoh mysingal yang berbeda, satu sama lain tidak akan dapat mendeteksi sinyal satu sama lain. Data yang akan diproses dapat disimpan di area cache bersama, yang disimpan secara terpisah dari instance MySignal.
Tunggu yang sibuk
Thread B yang sedang bersiap untuk memproses data sedang menunggu data tersedia. Dengan kata lain, ia menunggu sinyal dari utas A, yang menyebabkan hasdatatoprocess () mengembalikan true. Thread B berjalan dalam loop untuk menunggu sinyal ini:
Dilindungi MySignal SharedSignal = ...... while (! SharedSignal.HasDataToProcess ()) {// tidak melakukan apa -apa ... sibuk menunggu}tunggu (), beri tahu () dan notifyall ()
Menunggu sibuk tidak secara efektif memanfaatkan CPU yang menjalankan utas tunggu kecuali waktu tunggu rata -rata sangat singkat. Kalau tidak, lebih bijaksana untuk membuat benang menunggu tidur atau tidak berjalan sampai menerima sinyal yang ditunggu-tunggu.
Java memiliki mekanisme menunggu bawaan untuk memungkinkan benang menjadi tidak berjalan sambil menunggu sinyal. Kelas Java.lang.Object mendefinisikan tiga metode, tunggu (), notify () dan notifyall (), untuk mengimplementasikan mekanisme tunggu ini.
Setelah utas memanggil metode tunggu () dari objek apa pun, itu menjadi keadaan yang tidak berjalan sampai utas lain memanggil metode pemberitahuan () dari objek yang sama. Untuk menelepon tunggu () atau notify (), utas harus terlebih dahulu mendapatkan kunci objek itu. Artinya, utas harus menelepon tunggu () atau memberi tahu () di blok sinkronisasi. Berikut ini adalah versi mysingal yang dimodifikasi - mywaitnotify menggunakan tunggu () dan notify ():
kelas publik monitorObject {} kelas publik mywaitnotify {monitorObject mymonitorObject = new monitorObject (); public void dowait () {disinkronkan (mymonitorObject) {coba {mymonitorobject.wait (); } catch (InterruptedException e) {...}}} public void donotify () {disinkronkan (myMonitorObject) {mymonitorObject.notify (); }}}Utas menunggu akan menghubungi dowait (), sedangkan utas bangun akan menghubungi Donotify (). Ketika utas memanggil metode pemberitahuan () dari suatu objek, salah satu utas yang menunggu objek akan dibangunkan dan diizinkan untuk dieksekusi (Catatan: Thread ini akan dibangunkan adalah acak, dan tidak dapat ditentukan utas mana yang akan bangun). Juga disediakan adalah metode notifyall () untuk membangunkan semua utas menunggu objek yang diberikan.
Seperti yang Anda lihat, apakah itu utas menunggu atau utas bangun, itu memanggil tunggu () dan memberi tahu () di blok sinkronisasi. Ini wajib! Jika utas tidak menahan kunci objek, itu tidak dapat menelepon tunggu (), notify () atau notifyall (). Kalau tidak, pengecualian IllegalmonitorStateException akan dilemparkan.
(Catatan: Beginilah cara JVM diimplementasikan. Saat Anda menelepon tunggu, pertama -tama memeriksa apakah utas saat ini adalah pemilik kunci, dan melempar ilegalmonitorStateExcept.)
Tapi bagaimana ini mungkin? Saat menunggu utas dieksekusi di blok sinkronisasi, bukankah selalu memegang kunci objek monitor (objek mymonitor)? Dapatkah utas yang menunggu memblokir utas bangun yang memasuki blok sinkron Donotify ()? Jawabannya adalah: Itu benar. Setelah utas memanggil metode tunggu (), ia melepaskan kunci pada objek monitor yang ditahan. Ini akan memungkinkan utas lain untuk menelepon tunggu () atau memberi tahu () juga.
Setelah utas terbangun, panggilan metode tunggu () tidak dapat keluar segera sampai notify () dipanggil.
kelas publik mywaitnotify2 {monitorObject mymonitorObject = new monitorObject (); boolean wassignaled = false; public void dowait () {disinkronkan (mymonitorObject) {if (! wassignaled) {coba {mymonitorobject.wait (); } Catch (InterruptedException E) {...}} // Hapus sinyal dan lanjutkan berjalan. wassignaled = false; }} public void donotify () {disinkronkan (mymonitorObject) {wassignaled = true; mymonitorObject.notify (); }}}
Utas keluar dari blok sinkronisasi sendiri. Dengan kata lain, utas bangun harus mendapatkan kembali kunci objek monitor sebelum dapat keluar dari panggilan metode tunggu (), karena panggilan metode tunggu berjalan di blok sinkronisasi. Jika beberapa utas dibangunkan oleh NotifyAll (), maka pada saat yang sama hanya satu utas yang dapat keluar dari metode tunggu (), karena setiap utas harus mendapatkan kunci objek monitor sebelum keluar dari tunggu ().
Sinyal yang terlewatkan
Metode notify () dan notifyAll () tidak menyimpan metode yang memanggil mereka, karena ketika kedua metode ini dipanggil, ada kemungkinan bahwa tidak ada utas dalam keadaan menunggu. Sinyal pemberitahuan dibuang. Oleh karena itu, jika utas memanggil notify () sebelum diberi tahu sebelum menelepon tunggu (), utas tunggu akan kehilangan sinyal ini. Ini mungkin atau mungkin tidak menjadi masalah. Namun, dalam beberapa kasus, ini mungkin membuat utas menunggu selalu menunggu dan tidak lagi bangun karena utasnya melewatkan sinyal bangun.
Untuk menghindari kehilangan sinyal, mereka harus disimpan di kelas sinyal. Dalam contoh MyWaitNotify, sinyal pemberitahuan harus disimpan dalam variabel anggota dari myWaitNoTIFY. Berikut adalah versi MyWaitNotify yang dimodifikasi:
kelas publik mywaitnotify2 {monitorObject mymonitorObject = new monitorObject (); boolean wassignaled = false; public void dowait () {disinkronkan (mymonitorObject) {if (! wassignaled) {coba {mymonitorobject.wait (); } Catch (InterruptedException E) {...}} // Hapus sinyal dan lanjutkan berjalan. wassignaled = false; }} public void donotify () {disinkronkan (mymonitorObject) {wassignaled = true; mymonitorObject.notify (); }}}Perhatikan bahwa metode Donotify () menetapkan variabel yang ditinjau ke true sebelum memanggil notify (). Pada saat yang sama, perhatikan bahwa metode Dowait () memeriksa variabel yang diterjemahkan sebelum menelepon tunggu (). Bahkan, jika tidak ada sinyal yang diterima selama periode waktu antara panggilan dowait () sebelumnya dan panggilan dowait () ini, itu hanya akan menghubungi tunggu ().
(Bukti Catatan: Untuk menghindari kehilangan sinyal, gunakan variabel untuk menyimpan apakah telah diberitahu. Sebelum memberi tahu, atur diri Anda untuk diberi tahu. Setelah menunggu, atur diri Anda untuk belum diberitahu dan perlu menunggu pemberitahuan.)
Bangun palsu
Untuk beberapa alasan, ada kemungkinan bahwa utas bangun tanpa menelepon notify () dan notifyall (). Ini disebut bangun palsu. Bangun tanpa alasan.
Jika bangun yang salah terjadi dalam metode dowait () dari mywaitnotify2, utas menunggu dapat melakukan operasi selanjutnya bahkan jika tidak menerima sinyal yang benar. Ini dapat menyebabkan masalah serius dengan aplikasi Anda.
Untuk mencegah bangun palsu, variabel anggota yang menahan sinyal akan diperiksa dalam loop sementara, bukan pada ekspresi IF. Loop sementara itu disebut spin lock (Catatan: Pendekatan ini harus berhati -hati. Spin implementasi JVM saat ini mengkonsumsi CPU. Jika metode donotify tidak dipanggil untuk waktu yang lama, metode Dowait akan berputar terus menerus, dan CPU akan terlalu banyak mengkonsumsi). Benang yang terbangun akan berputar sampai kondisi di kunci spin (sementara loop) menjadi salah. Versi modifikasi myWaitNotify2 berikut ini menunjukkan ini:
kelas publik mywaitnotify3 {monitorObject mymonitorObject = new monitorObject (); boolean wassignaled = false; public void dowait () {disinkronkan (mymonitorObject) {while (! wassignaled) {coba {mymonitorobject.wait (); } Catch (InterruptedException E) {...}} // Hapus sinyal dan lanjutkan berjalan. wassignaled = false; }} public void donotify () {disinkronkan (mymonitorObject) {wassignaled = true; mymonitorObject.notify (); }}}Perhatikan bahwa metode tunggu () ada di loop while, bukan dalam ekspresi if. Jika utas yang menunggu bangun tanpa menerima sinyal, variabel yang diterjemahkan akan menjadi salah, dan loop sementara akan dieksekusi lagi, mendorong utas bangun untuk kembali ke keadaan menunggu.
Beberapa utas menunggu sinyal yang sama
Jika Anda memiliki banyak utas yang menunggu dan dibangunkan oleh NotifyAll (), tetapi hanya satu yang diizinkan untuk melanjutkan eksekusi, menggunakan loop sementara juga merupakan cara yang baik. Hanya satu utas yang bisa mendapatkan kunci objek monitor setiap kali, yang berarti bahwa hanya satu utas yang dapat keluar dari panggilan tunggu () dan menghapus bendera yang diterjemahkan (diatur ke false). Setelah utas ini keluar dari blok sinkronisasi dowait (), utas lain keluar dari panggilan tunggu () dan periksa nilai variabel yang diterjemahkan pada loop while. Namun, bendera ini telah dibersihkan oleh utas yang dibangunkan pertama, sehingga sisa benang yang terbangun akan kembali ke keadaan menunggu sampai waktu berikutnya sinyal tiba.
Jangan menelepon tunggu () dalam konstanta string atau objek global
(Bukti Catatan: Konstanta string yang disebutkan dalam bab ini mengacu pada variabel dengan nilai konstan)
Versi sebelumnya dari artikel ini menggunakan konstanta string ("") sebagai objek pipa dalam contoh mywaitnotify. Inilah contohnya:
kelas publik mywaitnotify {string mymonitorObject = ""; boolean wassignaled = false; public void dowait () {disinkronkan (mymonitorObject) {while (! wassignaled) {coba {mymonitorobject.wait (); } Catch (InterruptedException E) {...}} // Hapus sinyal dan lanjutkan berjalan. wassignaled = false; }} public void donotify () {disinkronkan (mymonitorObject) {wassignaled = true; mymonitorObject.notify (); }}}Masalah yang disebabkan oleh panggilan tunggu () dan beri tahu () di blok sinkronisasi string kosong sebagai kunci (atau string konstan lainnya) adalah bahwa JVM/kompiler akan mengubah string konstan menjadi objek yang sama. Ini berarti bahwa bahkan jika Anda memiliki 2 instance MyWaitnotify yang berbeda, mereka semua merujuk ke instance string kosong yang sama. Ini juga berarti bahwa ada risiko bahwa utas yang memanggil dowait () pada instance mywaitnotify pertama akan dibangunkan oleh utas yang memanggil donotify () pada instance mywaitnotify kedua. Situasi ini dapat ditarik sebagai berikut:
Pada awalnya ini mungkin bukan masalah besar. Lagi pula, jika donotify () dipanggil pada instance mywaitnotify kedua, yang sebenarnya terjadi adalah bahwa utas A dan B secara keliru dibangunkan. Benang bangun (A atau B) akan memeriksa nilai sinyal dalam loop while dan kemudian kembali ke keadaan menunggu, karena donotify () tidak dipanggil pada instance mywaitnotify pertama, dan ini adalah contoh yang ditunggu-tunggu. Situasi ini setara dengan memicu kebangkitan yang salah. Thread A atau B bangun tanpa nilai sinyal yang diperbarui. Tetapi kode menangani situasi ini, jadi utasnya kembali ke keadaan menunggu. Ingat, bahkan jika 4 utas panggilan tunggu () dan beri tahu () pada instance string bersama yang sama, sinyal di dowait () dan donotify () masing -masing akan disimpan oleh 2 mywaitnotify instance masing -masing. Panggilan donotify () di mywaitnotify1 dapat membangunkan utas mywaitnotify2, tetapi nilai sinyal hanya akan disimpan di mywaitnotify1.
Masalahnya adalah bahwa karena donotify () hanya panggilan notify () alih -alih notifyall (), bahkan jika ada 4 utas yang menunggu pada string (string kosong) yang sama, hanya satu utas yang dibangunkan. Jadi, jika utas A atau B dibangunkan oleh sinyal yang dikirim ke C atau D, ia akan memeriksa nilai sinyalnya sendiri untuk melihat apakah ada sinyal yang diterima dan kemudian kembali ke keadaan menunggu. Baik C maupun D tidak terbangun untuk memeriksa nilai sinyal yang mereka terima, sehingga sinyal hilang. Situasi ini setara dengan masalah sinyal yang hilang yang disebutkan di atas. C dan D telah dikirim ke sinyal, tetapi tidak ada yang bisa menanggapi sinyal.
Jika metode Donotify () Panggilan NotifyAll () alih -alih notify (), semua utas menunggu akan dibangunkan dan nilai sinyal akan diperiksa secara bergantian. Threads A dan B akan kembali ke keadaan menunggu, tetapi hanya satu utas di C atau D memperhatikan sinyal dan keluar dari panggilan metode dowait (). Yang lain dalam C atau D akan kembali ke keadaan menunggu karena utas yang memperoleh sinyal menghapus nilai sinyal (diatur ke false) selama proses keluar dari dowait ().
Setelah membaca paragraf di atas, Anda dapat mencoba menggunakan notifyall () alih-alih notify (), tetapi ini adalah ide buruk berbasis kinerja. Ketika hanya satu utas yang dapat menanggapi sinyal, tidak ada alasan untuk membangunkan semua utas setiap saat.
Jadi: dalam mekanisme tunggu ()/notify (), jangan gunakan objek global, konstanta string, dll. Objek unik yang sesuai harus digunakan. Misalnya, setiap instance mywaitnotify3 memiliki objek monitor sendiri alih -alih menelepon tunggu ()/notify () pada string kosong.
Di atas adalah informasi tentang java multi-threading dan komunikasi utas. Kami akan terus menambahkan informasi yang relevan di masa mendatang. Terima kasih atas dukungan Anda untuk situs ini!