Variabel volatil dalam bahasa Java dapat dianggap sebagai "disinkronkan lebih ringan"; Dibandingkan dengan blok yang disinkronkan, variabel volatil membutuhkan overhead pengkodean dan runtime yang lebih sedikit, tetapi fungsionalitas yang dapat dicapai hanyalah bagian dari sinkronisasi.
Kunci
Kunci menyediakan dua fitur utama: pengecualian dan visibilitas timbal balik.
Variabel yang mudah menguap
Variabel volatil memiliki karakteristik visibilitas yang disinkronkan, tetapi tidak memiliki karakteristik atom. Ini berarti bahwa utas dapat secara otomatis menemukan nilai terbaru dari variabel volatil.
Variabel volatil dapat digunakan untuk memberikan keamanan utas, tetapi hanya dapat diterapkan pada serangkaian kasus penggunaan yang sangat terbatas: tidak ada kendala antara beberapa variabel atau antara nilai variabel saat ini dan nilai yang dimodifikasi. Oleh karena itu, menggunakan volatile saja tidak cukup untuk mengimplementasikan penghitung, mutex, atau kelas apa pun dengan invarian yang terkait dengan beberapa variabel (mis. "Mulai <= end").
Untuk kesederhanaan atau skalabilitas, Anda mungkin cenderung menggunakan variabel volatil sebagai ganti kunci. Beberapa idiom lebih mudah dikodekan dan dibaca saat menggunakan variabel volatil daripada kunci. Selain itu, variabel volatil tidak menyebabkan penyumbatan benang seperti kunci, dan karenanya jarang menyebabkan masalah skalabilitas. Dalam beberapa kasus, variabel yang mudah menguap juga dapat memberikan keunggulan kinerja dibandingkan kunci jika operasi yang dibaca jauh lebih besar daripada operasi penulisan.
Kondisi untuk penggunaan variabel volatile yang benar
Anda hanya dapat menggunakan variabel yang mudah menguap alih -alih kunci dalam kasus terbatas. Untuk membuat variabel variabel yang variabel keamanan benang ideal, dua kondisi berikut harus dipenuhi pada saat yang sama:
Operasi tulis ke variabel tidak tergantung pada nilai saat ini.
Variabel ini tidak termasuk dalam invarian dengan variabel lain.
Faktanya, kondisi ini menunjukkan bahwa nilai -nilai yang valid yang dapat ditulis ke variabel yang mudah menguap tidak tergantung pada keadaan program apa pun, termasuk keadaan saat ini dari variabel.
Batas kondisi pertama mencegah variabel volatile digunakan sebagai penghitung yang aman. Meskipun operasi tambahan (X ++) terlihat seperti operasi yang terpisah, itu sebenarnya adalah operasi gabungan yang terdiri dari urutan operasi writ-write baca-modify yang harus dilakukan secara atom, dan volatil tidak dapat memberikan sifat atom yang diperlukan. Menerapkan operasi yang benar membutuhkan menjaga nilai konstan x selama operasi, yang tidak dimungkinkan dengan variabel volatil. (Namun, jika nilainya disesuaikan untuk ditulis hanya dari satu utas, kondisi pertama dapat diabaikan.)
Sebagian besar situasi pemrograman bertentangan dengan salah satu dari dua kondisi ini, membuat variabel yang mudah menguap tidak berlaku secara universal untuk keselamatan utas sebagaimana disinkronkan. Listing 1 menunjukkan kelas rentang numerik yang tidak aman-aman. Ini berisi invarian - batas bawah selalu kurang dari atau sama dengan batas atas.
Berikan contoh
Mari kita lihat contoh di bawah ini. Kami menerapkan penghitung. Setiap kali utas dimulai, metode Counter Inc akan dipanggil untuk menambahkan penghitung ke lingkungan eksekusi - Versi JDK: JDK1.6.0_31, Memori: 3G CPU: X86 2.4G
Counter kelas publik {public static int count = 0; public static void inc () {// penundaan di sini adalah 1 milidetik, membuat hasilnya jelas mencoba {thread.sleep (1); } catch (InterruptedException e) {} count ++; } public static void main (string [] args) {// Mulai 1000 utas pada saat yang sama untuk melakukan perhitungan i ++ dan melihat hasil aktual untuk (int i = 0; i <1000; i ++) {utas baru (runnable baru () {@override void run () {counter.inc ();}}). } // Nilai setiap menjalankan di sini mungkin berbeda, mungkin 1000 System.out.println ("Jalankan hasil: counter.count =" + counter.count); }} Hasil Menjalankan: Counter.Count = 995
Hasil operasi yang sebenarnya mungkin berbeda setiap saat. Hasil mesin adalah: menjalankan hasil: counter.count = 995. Dapat dilihat bahwa dalam lingkungan multi-threaded, hitungan tidak mengharapkan hasilnya 1000.
Banyak orang berpikir bahwa ini adalah masalah konkurensi multi-utas. Anda hanya perlu menambahkan volatile sebelum jumlah variabel untuk menghindari masalah ini. Kemudian kami memodifikasi kode untuk melihat apakah hasilnya memenuhi harapan kami.
penghitung kelas publik {public volatile static int count = 0; public static void inc () {// penundaan di sini adalah 1 milidetik, membuat hasilnya jelas mencoba {thread.sleep (1); } catch (InterruptedException e) {} count ++; } public static void main (String [] args) {// Mulai 1000 utas pada saat yang sama, lakukan perhitungan i ++, dan lihat hasil aktual untuk (int i = 0; i <1000; i ++) {utas baru (runnable baru () {@override void run () {counter.inc ();}} () {@override void public run () {counter.inc ();}} () {@override public run () {counter.inc ();} (} {@override public run () {counter. } // Nilai setiap menjalankan di sini mungkin berbeda, mungkin 1000 system.out.println ("Jalankan hasil: counter.count =" + counter.count); }}Hasil Menjalankan: Counter.Count = 992
Hasil operasi masih belum 1000 seperti yang kami harapkan. Mari kita analisis alasan di bawah ini
Dalam artikel koleksi sampah Java, alokasi memori pada saat JVM dijelaskan. Salah satu area memori adalah tumpukan mesin virtual JVM. Setiap utas memiliki tumpukan utas saat dijalankan, dan tumpukan utas menyimpan informasi nilai variabel selama utas berjalan. Ketika utas mengakses nilai objek tertentu, pertama -tama menemukan nilai variabel yang sesuai dengan memori heap melalui referensi objek, dan kemudian memuat nilai spesifik variabel memori heap ke dalam memori lokal utas untuk membuat salinan variabel. Setelah itu, utas tidak lagi memiliki hubungan dengan nilai variabel memori heap objek, tetapi secara langsung memodifikasi nilai variabel salinan, dan pada saat tertentu setelah modifikasi (sebelum utas keluar), secara otomatis menulis nilai variabel utas menyalin kembali ke variabel heap objek. Dengan cara ini nilai objek di tumpukan akan berubah. Gambar berikut menjelaskan interaksi tulisan ini
variabel salinan ead dan muat dari memori utama ke memori kerja saat ini
Gunakan dan Tetapkan Kode Etek untuk Mengubah Nilai Variabel Bersama
Simpan dan Tulis Refresh Konten Terkait Memori Utama dengan Data Memori Kerja
di mana penggunaan dan penetapan dapat muncul beberapa kali
Namun, operasi ini bukan atom, yaitu, setelah beban baca, jika variabel jumlah memori utama dimodifikasi, nilai dalam memori kerja utas tidak akan menyebabkan perubahan yang sesuai karena telah dimuat, sehingga hasil yang dihitung akan berbeda dari yang diharapkan.
Untuk variabel yang dimodifikasi dengan volatile, mesin virtual JVM hanya memastikan bahwa nilai yang dimuat dari memori utama ke memori yang berfungsi adalah yang terbaru adalah yang terbaru
Misalnya, jika utas 1 dan utas 2 melakukan operasi baca dan beban, dan temukan bahwa nilai jumlah dalam memori utama adalah 5, maka nilai terbaru akan dimuat
Setelah jumlah tumpukan dimodifikasi dalam utas 1, itu akan ditulis ke dalam memori utama, dan variabel hitungan dalam memori utama akan menjadi 6.
Karena Thread 2 telah melakukan operasi baca dan beban, nilai variabel dari jumlah memori utama juga akan diperbarui menjadi 6 setelah operasi.
Hal ini menyebabkan konkurensi terjadi setelah dua utas dimodifikasi dengan kata kunci yang mudah menguap dalam waktu.