Kata pengantar
Artikel sebelumnya berbicara tentang prinsip CAS, yang menyebutkan kelas atom. Mekanisme untuk mengimplementasikan operasi atom bergantung pada karakteristik visibilitas memori volatile. Jika Anda belum tahu CAS dan Atomic*, disarankan untuk melihat apa yang sedang dibicarakan oleh Cas Spin Kunci yang sedang kita bicarakan.
Tiga karakteristik konkurensi
Pertama-tama, jika kita ingin menggunakan volatile, itu harus berada di lingkungan konkurensi multi-utas. Ada tiga karakteristik penting dalam skenario bersamaan yang sering kita bicarakan: atomisitas, visibilitas, dan ketertiban. Hanya ketika ketiga karakteristik ini dipenuhi, program bersamaan dapat dieksekusi dengan benar, jika tidak berbagai masalah akan muncul.
Kelas atomisitas, CAS dan atom* yang disebutkan dalam artikel sebelumnya dapat memastikan atomisitas operasi sederhana. Untuk beberapa operasi yang bertanggung jawab, ini dapat diimplementasikan menggunakan kunci yang disinkronkan atau berbagai kunci.
Visibilitas mengacu ketika beberapa utas mengakses variabel yang sama, satu utas memodifikasi nilai variabel, dan utas lainnya dapat segera melihat nilai yang dimodifikasi.
Pemesanan, urutan eksekusi program dijalankan dalam urutan kode, dan instruksi dilarang diatur ulang. Tampak wajar bahwa ini bukan masalahnya. Instruksi ulang instruksi adalah JVM untuk mengoptimalkan instruksi dan meningkatkan efisiensi operasi program, dan untuk meningkatkan paralelisme sebanyak mungkin tanpa mempengaruhi hasil eksekusi dari program tunggal. Namun, dalam lingkungan multi-threaded, urutan beberapa kode dapat menyebabkan ketidaknyamanan logis.
Volatile mengimplementasikan dua fitur, visibilitas dan ketertiban. Oleh karena itu, dalam lingkungan multi-threaded, perlu untuk memastikan fungsi kedua fitur ini, dan kata kunci yang mudah menguap dapat digunakan.
Bagaimana volatile menjamin visibilitas
Ketika datang ke visibilitas, Anda perlu memahami prosesor komputer dan memori utama. Karena multi-threading, tidak peduli berapa banyak utas yang ada, pada akhirnya akan dilakukan dalam prosesor komputer. Komputer saat ini pada dasarnya multi-core, dan beberapa mesin bahkan memiliki multi-prosesor. Mari kita lihat diagram struktur dari multiprosesor:
Ini adalah CPU dengan dua prosesor, quad-core. Prosesor sesuai dengan slot fisik, dan beberapa prosesor terhubung melalui bus QPI. Prosesor terdiri dari beberapa core, dan cache L3 bersama multi-core antara prosesor. Inti berisi register, cache L1, cache L2.
Selama pelaksanaan program, membaca dan menulis data harus terlibat. Kita semua tahu bahwa meskipun kecepatan akses memori sudah sangat cepat, masih jauh lebih rendah daripada kecepatan instruksi pelaksanaan CPU. Oleh karena itu, dalam kernel, L1, L2, dan L3 level tiga cache ditambahkan. Dengan cara ini, ketika program berjalan, data yang diperlukan pertama kali disalin dari memori utama ke cache inti, dan setelah operasi selesai, kemudian ditulis ke memori utama. Gambar berikut adalah diagram skematis dari CPU yang mengakses data, dari register ke cache hingga memori utama dan bahkan hard disk, kecepatannya semakin lambat dan lebih lambat.
Setelah memahami struktur CPU, mari kita lihat proses spesifik pelaksanaan program dan ambil operasi peningkatan diri yang sederhana sebagai contoh.
i = i+1;
Saat menjalankan pernyataan ini, utas yang berjalan pada inti menyalin nilai I ke cache di mana inti berada. Setelah operasi selesai, itu akan ditulis kembali ke memori utama. Dalam lingkungan multi-threaded, setiap utas akan memiliki memori kerja yang sesuai di area cache pada inti yang berjalan, yaitu, masing-masing utas memiliki area cache kerja pribadi sendiri untuk menyimpan data replika yang diperlukan untuk operasi. Lalu, mari kita lihat masalah i+1. Dengan asumsi bahwa nilai awal I adalah 0, ada dua utas yang menjalankan pernyataan ini pada saat yang sama, dan setiap utas membutuhkan tiga langkah untuk dieksekusi:
1. Baca nilai I dari memori utama ke memori kerja utas, yaitu, area cache kernel yang sesuai;
2. Hitung nilai i+1;
3. Tulis nilai hasil kembali ke memori utama;
Setelah dua utas dieksekusi masing -masing 10.000 kali, nilai yang diharapkan harus 20.000. Sayangnya, nilai I selalu kurang dari 20.000. Salah satu alasan untuk masalah ini adalah masalah konsistensi cache. Untuk contoh ini, setelah salinan cache utas dimodifikasi, salinan cache utas lainnya harus segera dibatalkan.
Setelah menggunakan kata kunci yang mudah menguap, efek berikut adalah:
1. Setiap kali variabel dimodifikasi, cache prosesor (memori yang berfungsi) akan ditulis kembali ke memori utama;
2. Menulis kembali ke memori utama memori yang berfungsi akan menyebabkan cache prosesor (memori kerja) dari utas lain menjadi tidak valid.
Karena volatile memastikan visibilitas memori, itu sebenarnya menggunakan protokol MESI yang memastikan konsistensi cache oleh CPU. Ada banyak isi protokol MESI, jadi saya tidak akan menjelaskannya di sini. Silakan periksa sendiri. Singkatnya, kata kunci yang mudah menguap digunakan. Ketika modifikasi utas ke variabel volatil akan segera ditulis kembali ke memori utama, menyebabkan garis cache dari utas lain tidak valid, dan utas lainnya dipaksa untuk menggunakan variabel lagi, itu perlu dibaca dari memori utama.
Kemudian kami memodifikasi variabel I di atas dengan volatile dan menjalankannya lagi, setiap utas akan mengeksekusi 10.000 kali. Sayangnya, masih kurang dari 20.000. Mengapa ini?
Volatile menggunakan protokol MESI CPU untuk memastikan visibilitas. Namun, perhatikan bahwa volatile tidak menjamin atomisitas operasi, karena operasi pendakian diri ini dibagi menjadi tiga langkah. Misalkan Thread 1 membaca nilai I dari memori utama, dengan asumsi 10, dan penyumbatan terjadi pada saat ini, tetapi saya belum dimodifikasi. Pada saat ini, Thread 2 juga membaca nilai I dari memori utama. Pada saat ini, nilai I yang dibaca oleh kedua utas ini adalah sama, keduanya 10, dan kemudian utas 2 menambahkan 1 ke I dan segera menulisnya ke memori utama. Pada saat ini, menurut protokol MESI, garis cache yang sesuai dengan memori kerja Thread 1 akan diatur ke keadaan tidak valid, ya. Namun, harap dicatat bahwa Thread 1 telah menyalin nilai I dari memori utama, dan sekarang hanya perlu operasi menambahkan 1 dan menulis kembali ke memori utama. Kedua utas menambahkan 1 berdasarkan 10 dan kemudian menulis kembali ke memori utama, sehingga nilai akhir dari memori utama hanya 11, bukan 12 yang diharapkan.
Oleh karena itu, menggunakan volatile dapat memastikan visibilitas memori, tetapi tidak dapat menjamin atomisitas. Jika atomisitas masih diperlukan, Anda dapat merujuk ke artikel sebelumnya ini.
Bagaimana Stumlatile Memastikan Pesanan
Model memori Java memiliki "garis pesanan" bawaan, yaitu, dapat dijamin tanpa cara apa pun. Ini biasanya disebut prinsip yang terjadi sebelum. Jika urutan eksekusi dari dua operasi tidak dapat diturunkan dari prinsip yang terjadi sebelum, maka mereka tidak dapat menjamin ketertiban dan mesin virtual mereka dapat memesan ulang sesuka hati.
Berikut ini adalah 8 prinsip terjadi sebelum, dikutip dari "pemahaman mendalam tentang mesin virtual java".
Di sini kita terutama akan berbicara tentang aturan kata kunci yang mudah menguap, dan memberikan contoh pemeriksaan ganda dalam pola singleton yang terkenal:
kelas singleton {private volatile static singleton instance = null; private singleton () {} public static singleton getInstance () {if (instance == null) {// langkah 1 disinkronkan (singleton.class) {if (instance == null) // langkah 2 instance = singleton baru (); // Langkah 3}} Return instance; }}Jika instance tidak dimodifikasi dengan volatile, hasil apa yang dapat diproduksi? Misalkan ada dua utas yang memanggil metode getInstance (). Thread 1 mengeksekusi Step1 dan menemukan bahwa instance adalah nol, dan kemudian mengunci kelas singleton secara serempak. Kemudian menentukan apakah instance nol lagi, dan menemukan bahwa itu masih nol, dan kemudian mengeksekusi langkah 3 dan mulai instantiating singleton. Selama proses instantiasi, Thread 2 masuk ke langkah 1 dan mungkin menemukan bahwa instance tidak kosong, tetapi pada saat ini, instance mungkin tidak sepenuhnya diinisialisasi.
Apa artinya? Objek diinisialisasi dalam tiga langkah, dan diwakili oleh kode-pseudo berikut:
memori = alokasikan (); // 1. Alokasikan ruang memori dari objek ctorinstance (memori); // 2. Inisialisasi instance objek = memori; // 3. Atur ruang memori objek yang menunjuk ke objek
Karena Langkah 2 dan Langkah 3 perlu bergantung pada Langkah 1, dan Langkah 2 dan Langkah 3 tidak memiliki ketergantungan, ada kemungkinan bahwa kedua pernyataan ini akan menjalani pengumpulan instruksi, yaitu, atau mungkin bahwa langkah 3 akan dieksekusi sebelum langkah 2. Dalam hal ini, langkah 3 dijalankan, tetapi langkah 2 belum dieksekusi, yaitu, contoh belum diinisialisasi. Baru saja, Thread 2 menilai bahwa instance ini bukan nol, jadi langsung mengembalikan instance instance. Namun, pada saat ini, instance sebenarnya adalah objek yang tidak lengkap, jadi akan ada masalah saat menggunakannya.
Menggunakan kata kunci yang mudah menguap berarti menggunakan prinsip "Menulis variabel yang dimodifikasi dengan volatile, terjadi sebelum membaca variabel pada waktu berikutnya" sesuai dengan proses inisialisasi di atas. Langkah 2 dan 3 keduanya adalah instance menulis, jadi mereka harus terjadi nanti ketika membaca instance, yaitu, tidak akan ada kemungkinan mengembalikan instance yang tidak sepenuhnya diinisialisasi.
JVM yang mendasari dilakukan melalui sesuatu yang disebut "penghalang memori". Penghalang memori, juga dikenal sebagai pagar memori, adalah seperangkat instruksi prosesor yang digunakan untuk menerapkan pembatasan berurutan pada operasi memori.
akhirnya
Melalui kata kunci yang mudah menguap, kami telah belajar tentang visibilitas dan ketertiban dalam pemrograman bersamaan, yang tentu saja hanya pemahaman sederhana. Untuk pemahaman yang lebih dalam, Anda harus mengandalkan teman sekelas Anda untuk mempelajarinya sendiri.
Artikel terkait
Apa saja kunci putaran CAS yang sedang kita bicarakan
Meringkaskan
Di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.