Kata pengantar
Terkadang, jika Anda menggunakan sinkronisasi hanya untuk membaca dan menulis satu atau dua bidang instance, tampaknya terlalu mahal. Kata kunci yang mudah menguap memberikan mekanisme bebas kunci untuk akses sinkron ke bidang instance. Jika domain dinyatakan sebagai volatile, kompiler dan mesin virtual tahu bahwa domain dapat diperbarui secara bersamaan oleh utas lain. Sebelum berbicara tentang kata kunci yang mudah menguap, kita perlu memahami konsep yang relevan dari model memori dan tiga karakteristik dalam pemrograman bersamaan: atomisitas, visibilitas, dan ketertiban.
1. Model memori Java dengan atomisitas, visibilitas dan ketertiban
Model memori Java menetapkan bahwa semua variabel ada di memori utama, dan setiap utas memiliki memori kerjanya sendiri. Semua operasi utas pada variabel harus dilakukan dalam memori kerja, dan tidak dapat secara langsung beroperasi pada memori utama. Dan setiap utas tidak dapat mengakses memori kerja utas lain.
Di Java, jalankan pernyataan berikut:
int i = 3;
Utas eksekusi pertama -tama harus menetapkan garis cache di mana variabel I berada di utas kerjanya sendiri, dan kemudian menulisnya ke memori utama. Alih -alih menulis nilai 3 langsung ke memori utama.
Jadi jaminan apa yang diberikan bahasa Java sendiri untuk atomisitas, visibilitas, dan ketertiban?
Atomisitas
Operasi Baca dan Penugasan Variabel dari Tipe Data Dasar adalah Operasi Atom, yaitu, operasi ini tidak dapat terganggu dan dieksekusi atau tidak.
Mari kita lihat kode berikut:
x = 10; // pernyataan 1y = x; // pernyataan 2x ++; // pernyataan 3x = x + 1; // Pernyataan 4
Hanya pernyataan 1 yang merupakan operasi atom, dan tidak satu pun dari tiga pernyataan lainnya yang merupakan operasi atom.
Pernyataan 2 sebenarnya berisi 2 operasi. Pertama -tama perlu membaca nilai x, dan kemudian tulis nilai X ke memori kerja. Meskipun dua operasi membaca nilai X dan menulis nilai X ke memori kerja adalah operasi atom, mereka bukan operasi atom bersama.
Demikian pula, x ++ dan x = x+1 termasuk 3 operasi: Baca nilai x, lakukan operasi menambahkan 1, dan tulis nilai baru.
Dengan kata lain, hanya bacaan dan penugasan sederhana (dan nomor tersebut harus ditetapkan ke variabel, dan penugasan timbal balik antar variabel bukanlah operasi atom) adalah operasi atom.
Ada banyak kelas di java.util.concurrent. Paket atom yang menggunakan instruksi tingkat mesin yang sangat efisien (bukan kunci) untuk memastikan atomisitas operasi lain. Misalnya, kelas AtomicInteger menyediakan metode Incrementandget dan Decrementandget, yang masing -masing meningkatkan dan mengurangi bilangan bulat secara atom. Kelas AtomicInteger dapat dengan aman digunakan sebagai penghitung bersama tanpa sinkronisasi.
Selain itu, paket ini juga berisi kelas atom seperti atomicboolean, atomiclong dan atomicreference untuk pemrogram sistem yang hanya mengembangkan alat bersamaan, dan pemrogram aplikasi tidak boleh menggunakan kelas -kelas ini.
Visibilitas
Visibilitas mengacu pada visibilitas antara utas, dan keadaan yang dimodifikasi dari satu utas terlihat oleh utas lain. Itulah hasil modifikasi utas. Utas lain akan segera terlihat.
Ketika variabel bersama dimodifikasi dengan volatile, itu memastikan bahwa nilai yang dimodifikasi akan segera diperbarui ke memori utama, sehingga terlihat oleh utas lain. Ketika utas lain perlu dibaca, itu akan membaca nilai baru dalam memori.
Namun, variabel bersama biasa tidak dapat menjamin visibilitas, karena tidak pasti kapan variabel bersama normal ditulis ke memori utama setelah dimodifikasi. Ketika utas lain membacanya, nilai lama asli mungkin masih ada dalam memori, sehingga visibilitas tidak dapat dijamin.
Tertib
Dalam model memori Java, kompiler dan prosesor diizinkan untuk memesan ulang instruksi, tetapi proses pemesanan ulang tidak akan mempengaruhi pelaksanaan program tunggal, tetapi akan mempengaruhi kebenaran eksekusi bersamaan multi-threaded.
Kata kunci yang mudah menguap dapat digunakan untuk memastikan "garis pesanan" tertentu. Selain itu, sinkronisasi dan kunci dapat digunakan untuk memastikan pesanan. Jelas, disinkronkan dan kunci memastikan bahwa ada utas yang menjalankan kode sinkronisasi pada setiap momen, yang setara dengan membiarkan utas menjalankan kode sinkronisasi secara berurutan, yang secara alami memastikan pesanan.
2. Kata kunci yang mudah menguap
Setelah variabel bersama (variabel anggota kelas, variabel anggota statis kelas) dimodifikasi dengan mudah menguap, ia memiliki dua lapisan semantik:
Mari kita lihat sepotong kode terlebih dahulu. Jika utas 1 dieksekusi terlebih dahulu dan utas 2 dieksekusi nanti:
// Thread 1Boolean stop = false; while (! stop) {dosomething ();} // thread 2stop = true; Banyak orang dapat menggunakan metode markup ini saat mengganggu utas. Namun pada kenyataannya, akankah kode ini berjalan dengan benar? Apakah utas akan terganggu? Belum tentu. Mungkin sebagian besar waktu, kode ini dapat mengganggu utas, tetapi juga dapat menyebabkan utas tidak terganggu (meskipun kemungkinan ini sangat kecil, begitu ini terjadi, itu akan menyebabkan loop mati).
Mengapa mungkin menyebabkan kegagalan utas mengganggu? Setiap utas memiliki memori kerjanya sendiri selama proses berjalan. Saat utas 1 sedang berjalan, itu akan menyalin nilai variabel berhenti dan memasukkannya ke dalam memori kerjanya sendiri. Kemudian ketika Thread 2 mengubah nilai variabel berhenti, tetapi belum punya waktu untuk menulisnya ke memori utama, Thread 2 pergi untuk melakukan hal -hal lain, maka Thread 1 tidak tahu tentang perubahan Thread 2 ke variabel berhenti, sehingga akan terus melingkar.
Tetapi setelah memodifikasi dengan volatile itu menjadi berbeda:
Apakah volatile menjamin atomisitas?
Kita tahu bahwa kata kunci yang mudah menguap memastikan visibilitas operasi, tetapi dapatkah volatile memastikan bahwa operasi pada variabel adalah atom?
tes kelas publik {public volatile int inc = 0; public void peningkatan () {Inc ++; } public static void main (string [] args) {final test test = new test (); untuk (int i = 0; i <10; i ++) {thread baru () {public void run () {for (int j = 0; j <1000; j ++) test.increase (); }; }.awal(); } // Pastikan utas sebelumnya telah menyelesaikan eksekusi sementara (thread.activeCount ()> 1) thread.yield (); System.out.println (test.inc); }} Hasil kode ini tidak konsisten setiap kali berjalan. Itu adalah angka kurang dari 10.000. Seperti yang disebutkan sebelumnya, operasi pendakian otomatis bukan atom. Ini termasuk membaca nilai asli dari suatu variabel, melakukan operasi 1 tambahan, dan menulis ke memori yang berfungsi. Dengan kata lain, tiga sub-operasi dari operasi pendakian diri dapat dieksekusi secara terpisah.
Jika nilai variabel Inc adalah 10 pada waktu tertentu, Thread 1 melakukan operasi peningkatan diri pada variabel, Thread 1 pertama kali membaca nilai asli variabel Inc, dan kemudian utas 1 diblokir; Kemudian Thread 2 melakukan operasi pendakian diri pada variabel, dan Thread 2 juga membaca nilai asli variabel inc. Karena Thread 1 hanya melakukan operasi baca pada variabel Inc dan tidak memodifikasi variabel, itu tidak akan menyebabkan garis cache variabel cache Inc di Thread 2 menjadi tidak valid dalam memori kerja, sehingga Thread 2 akan langsung menuju ke memori utama untuk membaca nilai Inc. Ketika ditemukan bahwa nilai Inc adalah 10, kemudian melakukan peningkatan 1, dan menulis 11 ke memori kerja, dan akhirnya menulisnya ke memori utama. Kemudian utas 1 lalu lakukan operasi penambahan. Karena nilai Inc telah dibaca, perhatikan bahwa nilai Inc dalam Thread 1 masih 10 saat ini, jadi setelah Thread 1 menambahkan Inc, nilai Inc adalah 11, kemudian menulis 11 untuk bekerja memori, dan akhirnya menulisnya ke memori utama. Kemudian setelah kedua utas melakukan operasi penumpang diri, Inc hanya meningkat sebesar 1.
Operasi autoINCREMENT bukan operasi atom, dan volatile tidak dapat menjamin bahwa operasi apa pun pada variabel adalah atom.
Bisakah volatile memastikan keteraturan?
Seperti yang disebutkan sebelumnya, kata kunci yang mudah menguap dapat melarang pemesanan ulang instruksi, sehingga volatile dapat memastikan pesanan sampai batas tertentu.
Ada dua makna yang dilarang menyusun ulang kata kunci yang mudah menguap:
3. Gunakan kata kunci yang mudah menguap dengan benar
Kata kunci yang disinkronkan mencegah beberapa utas dari menjalankan sepotong kode secara bersamaan, yang akan sangat mempengaruhi efisiensi eksekusi program. Kinerja kata kunci yang mudah menguap lebih baik daripada disinkronkan dalam beberapa kasus. Namun, perlu dicatat bahwa kata kunci yang mudah menguap tidak dapat menggantikan kata kunci yang disinkronkan, karena kata kunci yang mudah menguap tidak dapat menjamin atomisitas operasi. Secara umum, dua kondisi berikut harus dipenuhi saat menggunakan volatile:
Kondisi pertama adalah bahwa ia tidak dapat beroperasi seperti pembangkangan diri dan penangguhan diri. Seperti disebutkan di atas, volatile tidak menjamin atomisitas.
Mari berikan contoh ini. Ini berisi invarian: Batas bawah selalu kurang dari atau sama dengan batas atas.
kelas publik numberrange {private volatile int lower, atas; publik int getlower () {return lebih rendah; } public int getUpper () {return atas; } public void setLower (value int) {if (value> Upper) Lempar IllegalArgumentException baru (...); lebih rendah = nilai; } public void setUpper (value int) {if (value <lower) Lempar IllegalArgumentException baru (...); atas = nilai; }} Dengan cara ini membatasi variabel -variabel keadaan terluka, sehingga mendefinisikan bidang bawah dan atas sebagai tipe volatil tidak sepenuhnya mengimplementasikan keamanan utas kelas, dan dengan demikian masih membutuhkan sinkronisasi. Kalau tidak, jika dua utas kebetulan menjalankan Setlower dan setupper dengan nilai yang tidak konsisten pada saat yang sama, kisaran akan tidak konsisten. Misalnya, jika keadaan awal adalah (0, 5), pada saat yang sama, utas panggilan A Panggilan (4) dan Thread B panggilan setupper (3), jelas bahwa nilai-nilai yang disimpan dalam silang yang disimpan oleh kedua operasi ini tidak memenuhi persyaratan, maka kedua utas akan melewati cek untuk melindungi invarian, sehingga nilai rentang terakhir adalah (4, 3), yang salah.
Bahkan, itu untuk memastikan atomisitas operasi untuk menggunakan volatile. Ada dua skenario utama untuk menggunakan volatile:
Bendera status
Shutdown -shutdown yang mudah menguap, ... ... public void shutdown () {shutdownRequested = true; } public void doWork () {while (! shutdown -recute) {// do stuff}}Kemungkinan bahwa metode shutdown () akan dipanggil dari luar loop - yaitu di utas lain - sehingga beberapa jenis sinkronisasi perlu dilakukan untuk memastikan visibilitas variabel shutdown yang diterjemahkan diterapkan dengan benar. Namun, menulis loop dengan blok yang disinkronkan jauh lebih merepotkan daripada menulis dengan bendera status yang mudah menguap. Karena volatile menyederhanakan pengkodean dan bendera status tidak bergantung pada keadaan lain dalam program, sangat cocok untuk volatile di sini.
Double Check Mode (DCL)
kelas publik singleton {private volatile static singleton instance = null; public static singleton getInstance () {if (instance == null) {disinkronkan (this) {if (instance == null) {instance = new singleton (); }} return instance; }} Menggunakan volatile di sini akan lebih atau kurang mempengaruhi kinerja, tetapi mempertimbangkan kebenaran program, masih layak untuk mengorbankan kinerja ini.
Keuntungan DCL adalah memiliki pemanfaatan sumber daya yang tinggi. Objek Singleton hanya dipakai ketika getInstance dieksekusi untuk pertama kalinya, yang sangat efisien. Kerugiannya adalah bahwa reaksinya sedikit lebih lambat saat memuat untuk pertama kalinya, dan ada cacat tertentu di lingkungan konkurensi yang tinggi, meskipun probabilitas kejadiannya sangat kecil.
Meskipun DCL memecahkan masalah konsumsi sumber daya, sinkronisasi yang tidak perlu, keamanan utas, dll. Sampai batas tertentu, masih memiliki masalah kegagalan dalam beberapa kasus, yaitu kegagalan DCL. Dalam buku "Java Concurrency Programming Practice", ini merekomendasikan menggunakan kode berikut (Pola Singleton Kelas Internal Statis) untuk menggantikan DCL:
kelas publik singleton {private singleton () {} public static singleton getInstance () {return singletonHolder.sinstance; } private static class singletonHolder {private static final singleton sinstance = new singleton (); }} Tentang cek ganda, Anda dapat melihat
4. Ringkasan
Dibandingkan dengan kunci, variabel volatil adalah mekanisme sinkronisasi yang sangat sederhana tetapi pada saat yang sama sangat rapuh yang dalam beberapa kasus akan memberikan kinerja dan skalabilitas yang lebih baik daripada kunci. Jika Anda secara ketat mengikuti kondisi penggunaan volatile yang benar -benar independen dari variabel lain dan nilai -nilai mereka sendiri sebelumnya, Anda dapat menggunakan volatile alih -alih disinkronkan untuk menyederhanakan kode dalam beberapa kasus. Namun, kode yang menggunakan volatile seringkali lebih rentan terhadap kesalahan daripada kode yang menggunakan kunci. Artikel ini memperkenalkan dua kasus penggunaan yang paling umum di mana volatil dapat digunakan alih -alih disinkronkan. Dalam kasus lain, kami lebih baik menggunakan disinkronkan.
Di atas adalah semua tentang artikel ini, saya harap ini akan membantu untuk pembelajaran semua orang.