Kata kunci yang disinkronkan
Sinkronisasi, kami memanggil kunci, terutama digunakan untuk mengunci metode dan blok kode. Ketika metode atau blok kode disinkronkan, paling banyak satu utas menjalankan kode secara bersamaan. Ketika beberapa utas mengakses blok metode penguncian/kode dari objek yang sama, hanya satu utas yang dijalankan pada saat yang sama, dan utas yang tersisa harus menunggu utas saat ini untuk menjalankan segmen kode sebelum dieksekusi. Namun, utas yang tersisa dapat mengakses blok kode yang tidak terkunci di objek.
Sinkronisasi terutama mencakup dua metode: metode yang disinkronkan dan blok yang disinkronkan.
Metode yang disinkronkan
Deklarasikan metode yang disinkronkan dengan menambahkan kata kunci yang disinkronkan ke deklarasi metode. menyukai:
void getResult publik yang disinkronkan ();
Metode yang disinkronkan mengontrol akses ke variabel anggota kelas. Bagaimana cara menghindari kontrol akses variabel anggota kelas? Kita tahu bahwa metode ini menggunakan kata kunci yang disinkronkan untuk menunjukkan bahwa metode ini terkunci. Ketika utas apa pun mengakses metode yang dimodifikasi, perlu untuk menentukan apakah utas lain adalah "eksklusif". Setiap instance kelas sesuai dengan kunci. Setiap metode yang disinkronkan harus memanggil kunci instance kelas dari metode sebelum dapat dieksekusi. Kalau tidak, utas tempatnya diblokir. Setelah metode dieksekusi, kunci akan eksklusif. Kunci tidak akan dilepaskan sampai kembali dari metode ini, dan benang yang diblokir dapat memperoleh kunci.
Bahkan, metode yang disinkronkan memiliki kekurangan. Jika kami mendeklarasikan metode besar yang disinkronkan, itu akan sangat mempengaruhi efisiensi. Jika beberapa utas mengakses metode yang disinkronkan, maka hanya satu utas yang menjalankan metode pada saat yang sama, sementara utas lain harus menunggu. Namun, jika metode ini tidak menggunakan sinkronisasi, semua utas dapat menjalankannya secara bersamaan, mengurangi waktu eksekusi total. Jadi, jika kita tahu bahwa suatu metode tidak akan dieksekusi oleh banyak utas atau tidak ada masalah berbagi sumber daya, kita tidak perlu menggunakan kata kunci yang disinkronkan. Tetapi jika kita harus menggunakan kata kunci yang disinkronkan, maka kita dapat mengganti metode yang disinkronkan dengan blok kode yang disinkronkan.
Blok yang disinkronkan
Blok kode yang disinkronkan memainkan peran yang sama dengan metode yang disinkronkan, kecuali bahwa itu membuat area kritis sesingkat mungkin. Dengan kata lain, ini hanya melindungi data yang dibutuhkan bersama, meninggalkan operasi ini untuk blok kode panjang yang tersisa. Sintaksnya adalah sebagai berikut:
Sinkronisasi (objek) {// Kode yang memungkinkan kontrol akses} Jika kita perlu menggunakan kata kunci yang disinkronkan dengan cara ini, kita harus menggunakan referensi objek sebagai parameter. Biasanya, kita sering menggunakan parameter ini sebagai ini.Ada pemahaman berikut tentang sinkronisasi (ini):
1. Ketika dua utas bersamaan mengakses blok kode sinkronisasi yang disinkronkan ini dalam objek objek yang sama, hanya satu utas yang dapat dieksekusi dalam satu waktu. Utas lain harus menunggu utas saat ini untuk menjalankan blok kode ini sebelum dapat menjalankan blok kode.
2. Namun, ketika satu utas mengakses satu blok kode sinkronisasi yang disinkronkan (ini) dari suatu objek, utas lain masih dapat mengakses blok kode sinkronisasi yang tidak disinkronkan (ini) di objek.
3. Sangat penting bahwa ketika utas mengakses blok kode sinkronisasi yang disinkronkan (ini) dari suatu objek, utas lain akan diblokir dari mengakses semua blok kode sinkronisasi yang disinkronkan (ini) pada objek.
4. Contoh ketiga juga berlaku untuk blok kode sinkronisasi lainnya. Artinya, ketika utas mengakses blok kode sinkronisasi yang disinkronkan (ini) dari suatu objek, ia memperoleh kunci objek objek ini. Akibatnya, utas lain akses ke semua bagian kode sinkron dari objek objek akan diblokir sementara.
Kunci
Ada prinsip "datang lebih dulu, datang nanti" di java multithreading, yaitu, siapa pun yang mengambil kunci terlebih dahulu akan menggunakannya terlebih dahulu. Kita tahu bahwa untuk menghindari masalah dengan kompetisi sumber daya, Java menggunakan mekanisme sinkronisasi untuk dihindari, dan mekanisme sinkronisasi dikendalikan menggunakan konsep kunci. Jadi bagaimana kunci tercermin dalam program Java? Di sini kita perlu mencari tahu dua konsep:
Apa itu kunci? Dalam kehidupan sehari -hari, ini adalah sealer yang ditambahkan ke pintu, kotak, laci dan benda -benda lain, yang mencegah orang lain mengintip atau mencuri, dan memainkan peran perlindungan. Hal yang sama berlaku di Java. Kunci berperan dalam melindungi objek. Jika utas secara eksklusif menempati sumber daya tertentu, maka tidak ingin menggunakan utas lain, tetapi ingin menggunakannya? Mari kita bicarakan saat saya selesai menggunakannya!
Dalam lingkungan yang menjalankan program Java, JVM perlu mengoordinasikan data yang dibagikan oleh dua jenis utas:
1. Variabel instance disimpan di heap
2. Variabel kelas disimpan di area metode.
Dalam mesin virtual Java, setiap objek dan kelas secara logis dikaitkan dengan monitor. Untuk suatu objek, monitor terkait melindungi variabel instance objek. Untuk kelas, monitor melindungi variabel kelas. Jika suatu objek tidak memiliki variabel instan, atau kelas tidak memiliki variabel, monitor terkait tidak akan memantau apa pun.
Untuk mengimplementasikan kemampuan pemantauan eksklusif monitor, mesin virtual Java mengaitkan kunci untuk setiap objek dan kelas. Mewakili hak istimewa yang hanya dapat dimiliki oleh satu utas kapan saja. Thread tidak perlu mengunci saat mengakses variabel instan atau variabel kelas. Jika utas memperoleh kunci, tidak mungkin bagi utas lain untuk mendapatkan kunci yang sama sebelum melepaskan kunci. Utas dapat mengunci objek yang sama beberapa kali. Untuk setiap objek, mesin virtual Java mempertahankan penghitung kunci. Setiap kali utas memperoleh objek, penghitung ditingkatkan sebesar 1, dan setiap kali dilepaskan, penghitung dikurangi 1. Ketika nilai penghitung adalah 0, kunci benar -benar dilepaskan.
Pemrogram Java tidak perlu menambahkan kunci sendiri, kunci objek digunakan secara internal oleh mesin virtual Java. Dalam program Java, Anda hanya perlu menggunakan blok yang disinkronkan atau metode yang disinkronkan untuk menandai area pemantauan. Setiap kali Anda memasuki area pemantauan, mesin virtual Java akan secara otomatis mengunci objek atau kelas.
Kunci sederhana
Saat menggunakan sinkronisasi, kami menggunakan kunci seperti ini:
Public Class ThreadTest {public void test () {disinkronkan (this) {// Do Something}}}Sinkronisasi memastikan bahwa hanya satu utas yang menjalankan dosomething pada saat yang sama. Berikut adalah penggunaan kunci alih -alih disinkronkan:
Public Class ThreadTest {lock lock = new lock (); public void test () {lock.lock (); // lakukan sesuatu lock.unlock (); }}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.
Apa yang terkunci?
Sebelum pertanyaan ini, kita harus jelas: apakah kata kunci yang disinkronkan ditambahkan ke metode atau objek, kunci yang diperolehnya adalah objek. Di Java, setiap objek dapat digunakan sebagai kunci, yang terutama tercermin dalam tiga aspek berikut:
Untuk metode sinkronisasi, kunci adalah objek instance saat ini.
Untuk blok metode sinkronisasi, kunci adalah objek yang dikonfigurasi dalam tanda kurung yang disinkronkan.
Untuk metode sinkronisasi statis, kunci adalah objek kelas dari objek saat ini.
Pertama, mari kita lihat contoh berikut:
Public Class ThreadTest_01 mengimplementasikan runnable {@Override public disinkronkan void run () {for (int i = 0; i <3; i ++) {System.out.println (thread.currentThread (). getName ()+"run ......"); }} public static void main (string [] args) {for (int i = 0; i <5; i ++) {thread baru (threadTest_01 (), "thread_"+i) .start (); }}}Hasil lari parsial:
Thread_2run ... thread_2run ... thread_4run ... thread_4run ... thread_3run ... thread_3run ... thread_3run ... thread_3run ... thread_2run ... thread_4run ...
Hasil ini sedikit berbeda dari hasil yang diharapkan (utas ini berjalan di sekitar sini). Secara logis, metode run ditambah kata kunci yang disinkronkan akan menghasilkan efek sinkronisasi. Utas ini harus menjalankan metode run satu demi satu. Seperti disebutkan di atas, setelah menambahkan kata kunci yang disinkronkan ke metode anggota, itu sebenarnya adalah kunci ke metode anggota. Titik spesifiknya adalah menggunakan objek itu sendiri di mana metode anggota terletak sebagai kunci objek. Namun, dalam contoh ini, kami memiliki 10 objek threadtest baru, dan setiap utas akan menahan kunci objek dari objek utasnya sendiri, yang pasti tidak akan menghasilkan efek sinkron. Jadi: jika utas ini akan disinkronkan, kunci objek yang dipegang oleh utas ini harus dibagikan dan unik!
Objek mana yang disinkronkan terkunci saat ini? Yang terkunci adalah menyebut objek metode sinkron ini. Dengan kata lain, jika objek ThreadTest menjalankan metode sinkronisasi di utas yang berbeda, itu akan membentuk saling eksklusif. Mencapai efek sinkronisasi. Jadi ubah utas baru di atas (threadTest_01 () baru (), "thread_" + i) .start (); ke utas baru (threadtest, "thread_" + i) .start ();
Untuk metode sinkronisasi, kunci adalah objek instance saat ini.
Contoh di atas menggunakan metode yang disinkronkan. Mari kita lihat blok kode yang disinkronkan:
Public Class ThreadTest_02 Extends Thread {Private String Lock; nama string pribadi; threadtest public_02 (nama string, string lock) {this.name = name; this.lock = lock; } @Override public void run () {disinkronkan (lock) {for (int i = 0; i <3; i ++) {System.out.println (name+"run ......"); }}} public static void main (string [] args) {string lock = new string ("test"); untuk (int i = 0; i <5; i ++) {threadTest_02 baru ("threadtest_"+i, lock) .start (); }}}Hasil Menjalankan:
ThreadTest_0 run ... threadtest_0 run ... threadtest_0 run ... threadtest_1 run ... threadtest_1 run ... threadtest_1 run ... threadtest_1 run ... threadtest_4 run ... threadtest_4 run ... run threadtest_4 run ... threadtest_3 run ... threadtest_3 run ... threadtest_3 run threadtest_2 run threadtest_3 run threadtest_3 run threadtest_3 run threadtest_2 run threadtest_2 run threadtest_2 run threadtest_2
Dalam metode utama, kami membuat kunci objek string dan menetapkan objek ini ke setiap kunci variabel pribadi objek threadtest2. Kita tahu bahwa ada string pool di java, sehingga variabel pribadi kunci dari utas ini sebenarnya menunjuk ke area yang sama dalam memori heap, yaitu, area di mana variabel kunci dalam fungsi utama disimpan, sehingga kunci objek unik dan dibagikan. Sinkronisasi Thread! Lai
Objek string kunci yang disinkronkan kunci di sini.
Untuk blok metode sinkronisasi, kunci adalah objek yang dikonfigurasi dalam tanda kurung yang disinkronkan.
Public Class ThreadTest_03 memperluas thread {public static void test () {for (int i = 0; i <3; i ++) {System.out.println (thread.currentThread (). getName ()+"run ......"); }} @Override public void run () {test (); } public static void main (string [] args) {for (int i = 0; i <5; i ++) {new threadTest_03 (). start (); }}}Hasil Menjalankan:
Thread-0 Run ... Thread-0 Run ... Thread-0 Run ... Thread-4 Run ... Thread-4 Run ... Thread-4 Run ... Thread-1 Run ... Thread-1 Run ... Thread-1 Run ... Thread-2 Run ... Thread-2 Run ... Thread-2 Run ... Thread-3 Run ... Thread-3 Run ... Thread-3 Run ...
Dalam contoh ini, metode run menggunakan metode sinkronisasi, dan metode sinkronisasi statis. Jadi apa kunci yang disinkronkan di sini? Kita tahu bahwa statis berada di luar objek dan berada di tingkat kelas. Oleh karena itu, kunci objek adalah contoh kelas dari kelas di mana rilis statis berada. Karena di JVM, semua kelas yang dimuat memiliki objek kelas yang unik, satu -satunya objek threadTest_03.class dalam contoh ini. Tidak peduli berapa banyak contoh kelas yang kami buat, instance kelasnya masih satu! Jadi kunci objek unik dan dibagikan. Sinkronisasi Thread! Lai
Untuk metode sinkronisasi statis, kunci adalah objek kelas dari objek saat ini.
Jika suatu kelas mendefinisikan fungsi statis yang disinkronkan A dan fungsi instance yang disinkronkan B, maka objek yang sama OBJ dari kelas ini tidak akan merupakan sinkronisasi ketika mengakses dua metode A dan B di beberapa utas, karena kuncinya berbeda. Kunci metode A adalah objek OBJ, sedangkan kunci B adalah kelas yang dimiliki OBJ.
Upgrade kunci
Ada empat negara bagian di Java: status bebas kunci, keadaan kunci yang bias, keadaan kunci ringan dan status kunci kelas berat, yang secara bertahap akan meningkat dengan persaingan. Kunci dapat ditingkatkan tetapi tidak dapat diturunkan, yang berarti bahwa kunci yang bias tidak dapat diturunkan ke kunci yang bias setelah ditingkatkan ke kunci ringan. Strategi peningkatan kunci ini tetapi tidak dapat diturunkan peringkatnya adalah untuk meningkatkan efisiensi memperoleh dan melepaskan kunci. Bagian utama di bawah ini adalah ringkasan blog: Concurrency (II) yang disinkronkan dalam Java SE1.6.
Putaran kunci
Kita tahu bahwa ketika utas memasuki metode sinkronisasi/blok kode, jika menemukan bahwa blok metode/kode sinkronisasi ditempati oleh orang lain, itu akan menunggu dan memasukkan status pemblokiran. Kinerja proses ini rendah.
Saat bertemu dengan kompetisi kunci, atau menunggu hal -hal, utasnya bisa kurang ingin memasuki status pemblokiran, tetapi tunggu dan lihat apakah kunci segera dilepaskan. Ini adalah putaran kunci. Sampai batas tertentu, putaran kunci dapat mengoptimalkan utas.
Kunci positif
Kunci positif terutama digunakan untuk menyelesaikan masalah kinerja kunci tanpa kompetisi. Dalam kebanyakan kasus, kunci kunci tidak hanya tidak memiliki kompetisi multi-utara, tetapi selalu diperoleh beberapa kali dengan utas yang sama. Untuk membuat utas mendapatkan kunci dengan biaya lebih rendah, kunci bias diperkenalkan. Ketika utas memperoleh kunci, utas dapat mengunci objek beberapa kali, tetapi setiap kali operasi semacam itu dilakukan, beberapa konsumsi overhead karena operasi CAS (CPU perbandingan-dan-swap). Untuk mengurangi overhead ini, kunci akan cenderung menjadi utas pertama untuk mendapatkannya. Jika kunci tidak diperoleh oleh utas lain selama proses eksekusi berikutnya, utas yang memegang kunci yang bias tidak perlu disinkronkan lagi.
Ketika utas lain mencoba untuk bersaing untuk kunci yang bias, utas yang menahan kunci yang bias melepaskan kunci.
Ekspansi kunci
Beberapa atau beberapa panggilan untuk mengunci dengan granularitas yang terlalu kecil tidak seefisien kunci panggilan dengan kunci granularitas besar.
Kunci ringan
Dasar untuk kunci ringan untuk meningkatkan kinerja sinkronisasi program adalah bahwa "untuk sebagian besar kunci, tidak ada persaingan di seluruh siklus sinkronisasi", yang merupakan data empiris. Kunci ringan menciptakan ruang yang disebut catatan kunci dalam bingkai tumpukan utas saat ini, yang digunakan untuk menyimpan penunjuk arus dan status objek kunci. Jika tidak ada kompetisi, kunci ringan menggunakan operasi CAS untuk menghindari overhead menggunakan mutex, tetapi jika ada kompetisi kunci, selain overhead mutex, operasi CAS terjadi tambahan, jadi dalam hal kompetisi, kunci ringan akan lebih lambat daripada kunci kelas berat tradisional.
Keadilan kunci
Kebalikan dari keadilan adalah kelaparan. Jadi apa itu "kelaparan"? Jika utas tidak bisa mendapatkan waktu berjalan CPU karena utas lain selalu menempati CPU, maka kami menyebut utas itu "lapar sampai mati". Solusi untuk kelaparan disebut "keadilan" - semua utas bisa mendapatkan peluang menjalankan CPU secara adil.
Ada beberapa alasan utama kelaparan benang:
Utas prioritas tinggi mengkonsumsi waktu CPU dari semua utas prioritas rendah. Kami dapat menetapkan prioritasnya secara individual untuk setiap utas, dari 1 hingga 10. Semakin tinggi utas prioritas, semakin banyak waktu yang diperlukan untuk mendapatkan CPU. Untuk sebagian besar aplikasi, sebaiknya tidak mengubah nilai prioritasnya.
Utas diblokir secara permanen dalam keadaan menunggu untuk memasuki blok sinkronisasi. Area kode sinkron Java adalah faktor penting yang menyebabkan kelaparan benang. Blok kode sinkron Java tidak menjamin urutan utas yang masuk. Ini berarti bahwa secara teori ada satu atau lebih utas yang selalu diblokir ketika mencoba memasuki area kode sinkron, karena utas lain selalu lebih unggul dari mereka untuk mendapatkan akses, mengakibatkannya tidak mendapatkan peluang menjalankan CPU dan "kelaparan sampai mati".
Utas sedang menunggu objek yang sendiri juga secara permanen menunggu penyelesaian. Jika beberapa utas berada pada eksekusi metode tunggu (), dan memanggil notify () di atasnya tidak menjamin bahwa utas apa pun akan dibangunkan, utas apa pun mungkin dalam keadaan menunggu terus menerus. Oleh karena itu, ada risiko bahwa satu utas yang menunggu tidak akan pernah terbangun, karena utas menunggu lainnya selalu dapat dibangunkan.
Untuk menyelesaikan masalah "kelaparan" utas, kita dapat menggunakan kunci untuk mencapai keadilan.
Reentrabilitas kunci
Kita tahu bahwa ketika utas meminta objek yang memegang kunci oleh utas lain, utas akan diblokir, tetapi dapatkah itu berhasil ketika utas meminta objek yang memegang kunci dengan sendirinya? Jawabannya adalah bahwa kesuksesan dapat berhasil, dan jaminan keberhasilan adalah "masuk kembali" dari kunci utas.
"Re -ententrable" berarti Anda bisa mendapatkan kunci internal Anda sendiri lagi tanpa memblokir. sebagai berikut:
Public Class Father {Metode Void Sinkronisasi Publik () {// Do Something}} Kelas Publik memperpanjang ayah {Metode void yang disinkronkan publik () {// lakukan sesuatu super.method (); }}
Jika tidak masuk kembali, kode di atas akan menemui jalan buntu, karena memanggil metode anak () pertama-tama akan memperoleh kunci bawaan dari ayah kelas induk dan kemudian memperoleh kunci anak bawaan. Saat memanggil metode kelas induk, Anda harus kembali ke kunci bawaan kelas induk lagi. Jika tidak masuk kembali, Anda mungkin jatuh ke jalan buntu.
Implementasi reentrabilitas java multithreading adalah untuk mengaitkan perhitungan permintaan dan utas yang menempati masing -masing kunci. Ketika jumlahnya adalah 0, diyakini bahwa kunci tidak ditempati, dan kemudian utas apa pun dapat memperoleh kepemilikan kunci. Ketika utas berhasil meminta, JVM akan merekam utas yang menahan kunci dan mengatur jumlah ke 1. Jika utas lain meminta kunci, mereka harus menunggu. Ketika utas meminta untuk mendapatkan kunci lagi, hitungan akan +1; Ketika utas yang menempati keluar dari blok kode sinkron, jumlahnya akan -1, sampai 0, kunci akan dilepaskan. Hanya dengan begitu utas lain dapat memiliki kesempatan untuk mendapatkan kepemilikan kunci.
Kunci dan kelas implementasinya
java.util.concurrent.locks menyediakan mekanisme penguncian yang sangat fleksibel, menyediakan antarmuka dan kelas kerangka kerja untuk mengunci dan kondisi menunggu. Ini berbeda dari sinkronisasi dan monitor bawaan, yang memungkinkan lebih banyak fleksibilitas dalam menggunakan penguncian dan kondisi. Diagram struktur kelasnya adalah sebagai berikut:
Reentrantlock: Kunci mutex reentrant, implementasi utama antarmuka kunci.
ReentrantReadWritelock:
ReadWritelock: ReadWritelock memelihara sepasang kunci terkait, satu untuk operasi hanya baca dan yang lainnya untuk operasi penulisan.
Semaphore: semaphore penghitungan.
Kondisi: Tujuan dari kunci adalah untuk memungkinkan utas mendapatkan kunci dan melihat apakah kondisi tertentu menunggu dipenuhi.
Cyclicbarrier: Kelas tambahan sinkron yang memungkinkan sekelompok utas untuk saling menunggu sampai mencapai titik penghalang yang umum.