Sejak Java 5, paket java.util.concurrent.locks berisi beberapa implementasi kunci, jadi Anda tidak perlu mengimplementasikan kunci Anda sendiri lagi. Tetapi Anda masih perlu memahami cara menggunakan kunci ini.
Kunci sederhana
Mari kita mulai dengan blok sinkronisasi di Java:
penghitung kelas publik {private int count = 0; public int inc () {disinkronkan (ini) {return ++ count; }}}Anda dapat melihat bahwa ada blok kode yang disinkronkan (ini) dalam metode Inc (). Blok kode ini dapat memastikan bahwa hanya satu utas yang dapat menjalankan Return ++ Count secara bersamaan. Meskipun kode dalam blok sinkronisasi yang disinkronkan dapat lebih kompleks, operasi sederhana dari ++ cukup untuk mengekspresikan arti sinkronisasi utas.
Kelas konter berikut menggunakan kunci alih -alih disinkronkan untuk mencapai tujuan yang sama:
counter kelas publik {private lock lock = new lock (); jumlah int private = 0; public int inc () {lock.lock (); int newcount = ++ count; lock.unlock (); mengembalikan newcount; }}Metode lock () mengunci objek instan kunci, sehingga semua utas yang memanggil metode pengunci () pada objek akan diblokir sampai metode buka kunci () dari objek kunci dipanggil.
Berikut adalah implementasi sederhana dari kelas kunci:
counter kelas publik {class public lock {private boolean islocked = false; lock void yang disinkronkan publik () melempar InterruptedException {while (islocked) {wait (); } islocked = true; } public disinkronkan void unlock () {islocked = false; memberitahu(); }}Perhatikan loop sementara (terkunci) di dalam, yang juga disebut "spin lock". Ketika islocked benar, lock calling utas () memblokir dan menunggu pada panggilan tunggu (). Untuk mencegah utas dari kembali dari tunggu () tanpa menerima panggilan notify () (juga disebut false preakeup), utas akan memeriksa kembali kondisi terkunci untuk menentukan apakah dapat dengan aman terus dieksekusi atau perlu terus menunggu lagi, alih -alih berpikir bahwa utas tersebut dapat terus dieksekusi setelah dibangunkan. Jika terkunci salah, utas saat ini akan keluar dari loop while (islocked) dan mengatur kembali terkunci kembali ke true, sehingga utas lain yang memanggil metode lock () dapat menambahkan kunci pada instance kunci.
Ketika utas melengkapi kode di bagian kritis (terletak di antara lock () dan buka kunci ()), buka kunci () dipanggil. Eksekusi unlock () akan disetel ulang terkunci ke false dan memberi tahu (bangun) salah satu utas yang disebut fungsi tunggu () dalam metode lock () dan dalam keadaan menunggu.
Reentrabilitas kunci
Blok sinkronisasi yang disinkronkan di Java reentrant. Ini berarti bahwa jika utas Java memasuki blok sinkronisasi yang disinkronkan dalam kode dan dengan demikian memperoleh kunci pada pipa yang sesuai dengan objek sinkronisasi yang digunakan oleh blok sinkronisasi, maka utas dapat memasukkan blok kode Java lain yang disinkronkan oleh objek pipa yang sama. Inilah contohnya:
kelas publik reentrant {publik sinkronisasi luar () {inner (); } public disinkronkan dalam () {// Do Something}}Perhatikan bahwa baik luar () dan dalam () dinyatakan disinkronkan, yang setara dengan blok yang disinkronkan (ini) di Java. Jika utas memanggil luar (), tidak ada masalah memanggil dalam () di luar (), karena kedua metode (blok kode) disinkronkan oleh objek manajemen yang sama ("ini"). Jika utas sudah memiliki kunci pada objek pipa, ia memiliki akses ke semua blok kode yang disinkronkan oleh objek pipa. Ini reentrant. Utas dapat memasukkan blok kode apa pun yang disinkronkan dengan kunci yang sudah ada.
Implementasi kunci yang diberikan di atas tidak masuk kembali. Jika kita menulis ulang kelas reentrant seperti berikut, ketika utas memanggil luar (), itu akan memblokir di lock.lock () dari metode dalam ().
kelas publik reentrant2 {lock lock = new lock (); Public Outer () {lock.lock (); batin(); lock.unlock (); } public disinkronkan dalam () {lock.lock (); // lakukan sesuatu lock.unlock (); }}Utas yang memanggil luar () pertama -tama akan mengunci instance kunci dan kemudian terus memanggil dalam (). Dalam metode dalam (), utas akan mencoba untuk mengunci instance kunci lagi, dan tindakan akan gagal (yaitu, utas akan diblokir), karena instance kunci sudah terkunci dalam metode luar ().
Jika buka kunci () tidak dipanggil antara kunci () dua kali, panggilan kedua untuk kunci akan diblokir. Setelah melihat implementasi LOCK (), Anda akan menemukan bahwa alasannya jelas:
kunci kelas publik {boolean islocked = false; lock void yang disinkronkan publik () melempar InterruptedException {while (islocked) {wait (); } islocked = true; } ...}Apakah utas dibiarkan keluar dari metode kunci () ditentukan oleh kondisi dalam loop while (spin lock). Kondisi penilaian saat ini adalah bahwa operasi kunci hanya diizinkan ketika islocked salah, tanpa mempertimbangkan utas mana yang menguncinya.
Untuk membuat kelas kunci ini dapat kembali, kita perlu membuat sedikit perubahan padanya:
kunci kelas publik {boolean islocked = false; Thread lockedby = null; int lockedcount = 0; lock void yang disinkronkan publik () melempar InterruptedException {thread callesThread = thread.currentThread (); while (islocked && lockedby! = CallingThread) {tunggu (); } islocked = true; LockedCount ++; Lockedby = CallingThread; } public disinkronkan void unlock () {if (thread.currentThread () == this.lockedby) {lockedcount--; if (lockedcount == 0) {islocked = false; memberitahu(); }}} ...}Perhatikan bahwa loop saat ini (kunci spin) juga memperhitungkan utas yang telah mengunci instance kunci. Jika objek kunci saat ini tidak terkunci (islocked = false), atau utas panggilan saat ini telah mengunci instance kunci, maka loop sementara tidak akan dieksekusi, dan kunci panggilan utas () dapat keluar dari metode (catatan penerjemah: "Diizinkan untuk keluar dari metode" dalam semantik saat ini berarti bahwa ia tidak akan menelepon tunggu () dan menyebabkan penyumbatan).
Selain itu, kita perlu merekam berapa kali utas yang sama berulang kali mengunci objek kunci. Kalau tidak, satu panggilan unblock () akan membuka blokir seluruh kunci, bahkan jika kunci saat ini telah dikunci berkali -kali. Kami tidak ingin kunci tidak terkunci sampai panggilan buka kunci () mencapai berapa kali panggilan kunci () yang sesuai dipanggil.
Sekarang kelas kunci ini masuk kembali.
Keadilan kunci
Blok yang disinkronkan Java tidak menjamin urutan utas yang mencoba memasukkannya. Oleh karena itu, jika beberapa utas terus bersaing untuk mengakses blok sinkronisasi sinkronisasi yang sama, ada risiko bahwa satu atau lebih utas tidak akan pernah mendapatkan akses - yaitu, akses selalu ditetapkan ke utas lain. Situasi ini disebut kelaparan benang. Untuk menghindari masalah ini, kunci perlu mencapai keadilan. Kunci yang ditunjukkan dalam artikel ini diimplementasikan secara internal dengan blok sinkronisasi yang disinkronkan, sehingga tidak dijamin adil.
Hubungi buka kunci () dalam pernyataan akhirnya
Jika kunci digunakan untuk melindungi area kritis, dan area kritis dapat melemparkan pengecualian, sangat penting untuk memanggil buka kunci () dalam pernyataan akhirnya. Ini memastikan bahwa objek kunci dapat dibuka sehingga utas lain dapat terus menguncinya. Inilah contohnya:
lock.lock (); coba {// lakukan kode bagian kritis, // yang mungkin melempar pengecualian} akhirnya {lock.unlock ();}Struktur sederhana ini memastikan bahwa objek kunci dapat dibuka ketika pengecualian dilemparkan ke area kritis. Jika buka kunci () tidak dipanggil dalam pernyataan akhirnya, ketika pengecualian dilemparkan ke bagian kritis, objek kunci akan tetap dalam keadaan terkunci selamanya, yang akan menyebabkan semua utas lain memanggil kunci () pada objek kunci untuk diblokir.
Di atas adalah informasi tentang penguncian java multi-threaded. Kami akan terus menambahkan informasi yang relevan di masa mendatang. Terima kasih atas dukungan Anda untuk situs ini!