Isi utama artikel ini adalah titik pengetahuan umum dalam wawancara Java: kata kunci yang mudah menguap. Artikel ini memperkenalkan semua aspek kata kunci yang mudah menguap secara detail. Saya harap setelah membaca artikel ini, Anda dapat dengan sempurna menyelesaikan masalah terkait kata kunci yang mudah menguap.
Dalam wawancara kerja terkait Java, banyak pewawancara suka memeriksa pemahaman pewawancara tentang konkurensi Java. Menggunakan kata kunci yang mudah menguap sebagai titik masuk kecil, Anda sering dapat menanyakan model memori Java (JMM) dan beberapa fitur pemrograman bersamaan Java. Secara mendalam, Anda juga dapat memeriksa pengetahuan implementasi JVM dan sistem operasi yang mendasari. Mari kita ambil proses wawancara hipotetis untuk mendapatkan pemahaman mendalam tentang kata kunci volitil!
Sejauh yang saya mengerti, variabel bersama yang dimodifikasi oleh Volatile memiliki dua karakteristik berikut:
1. Pastikan visibilitas memori dari berbagai utas ke operasi variabel;
2. Laratkan perintah pemesanan ulang
Ini banyak untuk dibicarakan, jadi saya akan mulai dengan model memori Java. Spesifikasi mesin virtual Java berupaya mendefinisikan model memori Java (JMM) untuk memblokir perbedaan akses memori antara berbagai perangkat keras dan sistem operasi, sehingga program Java dapat mencapai efek akses memori yang konsisten pada berbagai platform. Sederhananya, karena CPU menjalankan instruksi dengan sangat cepat, kecepatan akses memori jauh lebih lambat, dan perbedaannya bukan urutan besarnya, orang -orang besar yang bekerja pada prosesor telah menambahkan beberapa lapisan cache ke CPU. Dalam model memori Java, optimasi di atas disarikan lagi. JMM menetapkan bahwa semua variabel berada dalam memori utama, mirip dengan memori biasa yang disebutkan di atas, dan setiap utas berisi memori kerjanya sendiri. Ini dapat dianggap sebagai register atau cache pada CPU agar mudah dipahami. Oleh karena itu, operasi utas terutama didasarkan pada memori kerja. Mereka hanya dapat mengakses memori kerja mereka sendiri, dan mereka harus menyinkronkan nilai kembali ke memori utama sebelum dan sesudah bekerja. Saya bahkan tidak tahu apa yang saya katakan, ambil selembar kertas untuk menggambar:
Saat menjalankan utas, nilai variabel akan dibaca terlebih dahulu dari memori utama, kemudian dimuat ke salinan di memori yang berfungsi, dan kemudian meneruskannya ke prosesor untuk dieksekusi. Setelah eksekusi selesai, salinan dalam memori yang berfungsi akan diberi nilai, dan kemudian nilai dalam memori kerja akan diteruskan kembali ke memori utama, dan nilai dalam memori utama akan diperbarui. Meskipun penggunaan memori kerja dan memori utama lebih cepat, itu juga membawa beberapa masalah. Misalnya, lihat contoh berikut:
i = i + 1;
Dengan asumsi bahwa nilai awal i adalah 0, ketika hanya satu utas yang menjalankannya, hasilnya pasti akan mendapatkan 1. Ketika dua utas dieksekusi, akankah hasilnya mendapatkan 2? Ini belum tentu terjadi. Ini mungkin terjadi:
Thread 1: Load I dari memori utama // i = 0 i + 1 // i = 1 Thread 2: Load i dari memori utama // karena utas 1 belum menulis nilai saya kembali ke memori utama, saya masih 0 i + 1 // i = 1 utas 1: simpan saya ke utas memori utama 2: simpan ke ingatan utama utama saya
Jika dua utas mengikuti proses eksekusi di atas, maka nilai terakhir saya sebenarnya 1. Jika tulisan terakhirnya lambat, dan Anda dapat membaca nilai I lagi, itu mungkin 0, yang merupakan masalah inkonsistensi cache. Berikut ini adalah untuk menyebutkan pertanyaan yang baru saja Anda tanyakan. JMM terutama ditetapkan di sekitar bagaimana menangani tiga karakteristik atomisitas, visibilitas dan ketertiban dalam proses konkurensi. Dengan memecahkan ketiga masalah ini, masalah inkonsistensi cache dapat diselesaikan. Dan volatile terkait dengan visibilitas dan ketertiban.
1. Atomisitas: Di Java, operasi bacaan dan penugasan tipe data dasar adalah operasi atom. Operasi atom yang disebut berarti bahwa operasi ini tidak terputus dan harus diselesaikan untuk jangka waktu tertentu, atau mereka tidak akan dieksekusi. Misalnya:
i = 2; j = i; i ++; i = i+1;
Di antara empat operasi di atas, I = 2 adalah operasi baca, yang harus berupa operasi atom. J = Saya pikir ini adalah operasi atom. Bahkan, itu dibagi menjadi dua langkah. Salah satunya adalah membaca nilai i, dan kemudian menetapkan nilai ke j. Ini adalah operasi 2 langkah. Itu tidak dapat disebut operasi atom. i ++ dan i = i+ 1 sebenarnya setara. Baca nilai i, tambahkan 1, dan tuliskan kembali ke memori utama. Itu adalah operasi 3 langkah. Oleh karena itu, dalam contoh di atas, nilai terakhir mungkin memiliki banyak situasi karena tidak dapat memenuhi atomisitas. Dengan cara ini, hanya ada bacaan sederhana. Penugasan adalah operasi atom, atau hanya penugasan numerik. Jika Anda menggunakan variabel, ada operasi tambahan untuk membaca nilai variabel. Pengecualian adalah bahwa spesifikasi mesin virtual memungkinkan tipe data 64-bit (panjang dan ganda) diproses dalam 2 operasi, tetapi implementasi JDK terbaru masih mengimplementasikan operasi atom. JMM hanya mengimplementasikan atomisitas dasar. Operasi seperti I ++ di atas harus disinkronkan dan terkunci untuk memastikan atomisitas seluruh kode. Sebelum utas melepaskan kunci, pasti akan menyikat nilai saya kembali ke memori utama. 2. Visibilitas: Berbicara tentang visibilitas, Java menggunakan volatile untuk memberikan visibilitas. Ketika suatu variabel dimodifikasi dengan volatile, modifikasi untuk itu akan segera disegarkan ke memori utama. Ketika utas lain perlu membaca variabel, nilai baru akan dibaca dalam memori. Ini tidak dijamin oleh variabel biasa. Bahkan, sinkronisasi dan kunci juga dapat memastikan visibilitas. Sebelum utas melepaskan kunci, itu akan menyiram semua nilai variabel bersama kembali ke memori utama, tetapi disinkronkan dan kunci lebih mahal. 3. Memesan JMM memungkinkan kompiler dan prosesor untuk memesan ulang instruksi, tetapi menetapkan semantik as-if-serial, yaitu, tidak peduli bagaimana pemesanan ulang, hasil eksekusi dari program tidak dapat diubah. Misalnya, segmen program berikut:
PI ganda = 3.14; // Adouble r = 1; // bdouble s = pi * r * r; // c
Pernyataan di atas dapat dieksekusi dalam A-> B-> C, dengan hasilnya 3,14, tetapi juga dapat dieksekusi dalam urutan B-> A-> C. Karena A dan B adalah dua pernyataan independen, sedangkan C tergantung pada A dan B, A dan B dapat dipesan ulang, tetapi C tidak dapat diperingkat pertama dalam A dan B. JMM memastikan bahwa pemesanan ulang tidak akan mempengaruhi pelaksanaan satu utas, tetapi masalah rentan terjadi dalam multi-utusan. Misalnya, kode seperti ini:
int a = 0; bool flag = false; public void write () {a = 2; // 1 flag = true; // 2} public void multiply () {if (flag) {// 3 int ret = a * a; // 4}}Jika dua utas menjalankan segmen kode di atas, utas 1 pertama mengeksekusi tulis, lalu utas 2 kemudian mengeksekusi multiply, akankah nilai RET harus 4? Hasilnya belum tentu:
Seperti yang ditunjukkan pada gambar, 1 dan 2 dalam metode tulis disusun ulang. Thread 1 Pertama menetapkan bendera ke True, kemudian menjalankannya ke Thread 2, RET secara langsung menghitung hasilnya, dan kemudian ke Thread 1. Pada saat ini, A ditetapkan ke 2, yang jelas selangkah kemudian. Pada saat ini, Anda dapat menambahkan kata kunci yang mudah menguap ke bendera, melarang pemesanan ulang, yang dapat memastikan ketertiban program, dan Anda juga dapat menggunakan kelas berat yang disinkronkan dan mengunci untuk memastikan ketertiban. Mereka dapat memastikan bahwa kode di area itu dieksekusi pada satu waktu. Selain itu, JMM memiliki beberapa urutan bawaan, yaitu, ketertiban yang dapat dijamin tanpa cara apa pun, yang biasanya disebut prinsip yang terjadi sebelumnya. <<JSR-133: Java Memory Model and Thread Specification>> defines the following happens-before rules: 1. Program sequence rules: For each operation in a thread, happens-before is used for any subsequent operations in the thread 2. Monitor lock rules: Unlock a thread, happens-before is used for subsequent locking of this thread 3. volatile variable rules: Write a volatile domain, happens-before is used for subsequent reading of this Domain Volatile 4. Transitif: Jika A terjadi sebelum B dan B terjadi sebelum C, maka A yang terjadi sebelum C 5.-start () Aturan: Jika Thread A melakukan operasi threadb_start () (Mulai Thread B), maka Threadb_Start () terjadi di BREAF BEADBE THREADS (LALU PRINSIP BEAF BAGI APA PUN (LALU PRINSIP ATIRE. dari utas yang berhasil dikembalikan dari threadb.join () operasi di Thread A. 7. Prinsip interrupt (): Panggilan ke metode interupsi utas () terjadi terlebih dahulu ketika peristiwa interupsi terdeteksi oleh kode utas yang terganggu. Anda dapat menggunakan metode thread.interrupted () untuk mendeteksi apakah ada gangguan. 8. Prinsip finalisasi (): Penyelesaian inisialisasi suatu objek terjadi pertama kali ketika metode finalisasi () dimulai. Aturan pertama dari aturan urutan program mengatakan bahwa di utas, semua operasi berurutan, tetapi dalam JMM, selama hasil eksekusi sama, diizinkan ulang. Fokus terjadi sebelum di sini juga merupakan kebenaran dari hasil eksekusi utamanya, tetapi tidak dapat dijamin bahwa hal yang sama berlaku untuk multi-threading. Aturan 2 Aturan monitor sebenarnya mudah dimengerti. Sebelum menambahkan kunci, Anda hanya dapat terus menambahkan kunci. Aturan 3 berlaku untuk volatile yang dimaksud. Jika satu utas menulis variabel terlebih dahulu dan utas lain membacanya, maka operasi tulis harus sebelum operasi baca. Aturan keempat adalah transitivitas terjadi sebelum. Saya tidak akan membahas beberapa tentang beberapa orang berikut.
Maka kita perlu aturan variabel volatile yang disebutkan ulang: Tulis domain yang mudah menguap, terjadi sebelum membaca domain yang mudah menguap ini nanti. Biarkan saya mengeluarkan ini lagi. Bahkan, jika suatu variabel dinyatakan sebagai volatile, maka ketika saya membaca variabel, saya selalu dapat membaca nilai terbarunya. Di sini, nilai terbaru berarti bahwa utas mana pun yang menulis variabel, itu akan segera diperbarui ke memori utama. Saya juga dapat membaca nilai yang baru ditulis dari memori utama. Dengan kata lain, kata kunci yang mudah menguap dapat memastikan visibilitas dan ketertiban. Mari kita ambil kode di atas sebagai contoh:
int a = 0; bool flag = false; public void write () {a = 2; // 1 flag = true; // 2} public void multiply () {if (flag) {// 3 int ret = a * a; // 4}}Kode ini tidak hanya bermasalah dengan memesan ulang, bahkan jika 1 dan 2 tidak dipesan ulang. 3 juga tidak akan dieksekusi dengan sangat lancar. Misalkan utas 1 menjalankan operasi tulis terlebih dahulu, dan utas 2 kemudian melakukan operasi multipel. Karena Thread 1 memberikan bendera ke 1 dalam memori kerja, mungkin tidak akan ditulis kembali ke memori utama segera. Oleh karena itu, ketika utas 2 dieksekusi, multipel membaca nilai bendera dari memori utama, yang mungkin masih salah, sehingga pernyataan dalam tanda kurung tidak akan dieksekusi. Jika diubah menjadi berikut:
int a = 0; volatile bool flag = false; public void write () {a = 2; // 1 flag = true; // 2} public void multiply () {if (flag) {// 3 int ret = a * a; // 4}}Kemudian utas 1 mengeksekusi tulis terlebih dahulu, dan utas 2 kemudian mengeksekusi multiply. Menurut prinsip yang terjadi sebelum, proses ini akan memenuhi tiga jenis aturan berikut: Aturan Pesanan Program: 1 terjadi sebelum 2; 3 terjadi sebelum 4; (Volatile membatasi penyusunan ulang instruksi, jadi 1 dieksekusi sebelum 2) aturan volatil: 2 terjadi sebelum 3 aturan transitif: 1 terjadi sebelum 4 saat menulis variabel volatile, JMM akan menyiram variabel bersama dalam memori lokal yang sesuai dengan utas ke memori utama. Saat membaca variabel yang mudah menguap, JMM akan mengatur memori lokal yang sesuai dengan utas untuk membatalkan, dan utas akan membaca variabel bersama dari memori utama berikutnya.
Pertama -tama, jawaban saya adalah bahwa atomisitas tidak dapat dijamin. Jika dijamin, hanya atomisitas untuk membaca/menulis variabel volatil tunggal, tetapi tidak ada hubungannya dengan operasi majemuk seperti volatile ++, seperti contoh berikut:
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(); } while (thread.activeCount ()> 1) // Pastikan utas sebelumnya telah menyelesaikan thread.yield (); System.out.println (test.inc); }Secara logis, hasilnya adalah 10.000, tetapi kemungkinan nilainya kurang dari 10.000 saat berjalan. Beberapa orang mungkin mengatakan bahwa volatile tidak menjamin visibilitas. Satu utas harus melihat modifikasi ke Inc oleh Inc, dan utas lainnya harus segera melihatnya! Tetapi Operation Inc ++ di sini adalah operasi gabungan, termasuk membaca nilai Inc, meningkatkannya dengan sendirinya, dan kemudian menulisnya kembali ke memori utama. Misalkan utas A membaca nilai Inc menjadi 10, dan diblokir saat ini karena variabel tidak dimodifikasi dan aturan volatil tidak dapat dipicu. Thread B juga membaca nilai Inc saat ini. Nilai Inc dalam memori utama masih 10, dan akan secara otomatis ditingkatkan, dan kemudian akan ditulis kembali ke memori utama, yaitu 11. Pada saat ini, giliran utas A untuk mengeksekusi. Karena 10 disimpan dalam memori kerja, itu terus meningkat sendiri dan menulis kembali ke memori utama. 11 ditulis lagi. Jadi meskipun kedua utas tersebut dieksekusi meningkat () dua kali, mereka hanya menambahkan sekali. Beberapa orang berkata, tidakkah volatile membatalkan garis cache? Namun, sebelum utas membaca utas B dan melakukan operasi, nilai INC tidak dimodifikasi, jadi ketika utas B membaca, masih dibaca 10. Beberapa orang juga mengatakan bahwa jika utas B menulis 11 kembali ke memori utama, tidak akan mengatur garis cache Thread A untuk membatalkan? Namun, Thread A telah melakukan operasi baca. Hanya ketika operasi baca selesai dan garis cache tidak valid akan membaca nilai memori utama. Oleh karena itu, Thread A hanya dapat terus melakukan pendapatan diri. Singkatnya, dalam jenis operasi gabungan ini, fungsi atom tidak dapat dipertahankan. Namun, dalam contoh pengaturan nilai bendera di atas, karena operasi baca/tulis bendera adalah satu langkah, masih dapat memastikan atomisitas. Untuk memastikan atomisitas, kami hanya dapat menggunakan kelas operasi atom yang disinkronkan, terkunci, dan atom di bawah paket bersamaan, yaitu, meningkatnya self-increment (tambahkan 1 operasi), penangguhan diri (mengurangi 1 operasi), operasi penambahan (tambahkan angka), dan operasi pengurangan (kurangi satu angka) dari jenis data dasar untuk memastikan bahwa operasi ini adalah operasi atom.
Jika Anda menghasilkan kode perakitan dengan kata kunci yang mudah menguap dan kode tanpa kata kunci yang mudah menguap, Anda akan menemukan bahwa kode dengan kata kunci volatil akan memiliki instruksi awalan kunci tambahan. Instruksi awalan kunci sebenarnya setara dengan penghalang memori. Penghalang memori menyediakan fungsi -fungsi berikut: 1. Saat memesan ulang, instruksi berikut tidak dapat dipesan ulang ke lokasi sebelum penghalang memori 2. Buat cache CPU ini ditulis ke memori ** ** 3. Tindakan tulis juga akan menyebabkan CPU lain atau kernel lain untuk membatalkan cache mereka, yang setara dengan membuat nilai tertulis yang baru terlihat.
1. Tanda kuantitas status, sama seperti bendera di atas, saya akan menyebutkannya lagi:
int a = 0; volatile bool flag = false; public void write () {a = 2; // 1 flag = true; // 2} public void multiply () {if (flag) {// 3 int ret = a * a; // 4}}Operasi baca dan tulis ini ke variabel, ditandai sebagai volatil, dapat memastikan bahwa modifikasi segera terlihat oleh utas. Dibandingkan dengan sinkronisasi, LOCK memiliki peningkatan efisiensi tertentu. 2. Implementasi Mode Singleton, Kecek Double Check Lock (DCL) Khas
kelas singleton {private volatile static singleton instance = null; private singleton () {} public static singleton getInstance () {if (instance == null) {disinkronkan (singleton.class) {if (instance == null) instance = singleton baru (); }} return instance; }}Ini adalah pola singleton yang malas, objek dibuat hanya saat digunakan, dan untuk menghindari instruksi pemesanan ulang untuk operasi inisialisasi, volatile ditambahkan ke contoh.
Di atas adalah seluruh konten artikel ini tentang menjelaskan kata kunci yang mudah menguap yang ingin ditanyakan oleh pewawancara Java secara rinci. Saya harap ini akan membantu semua orang. Teman yang tertarik dapat terus merujuk ke topik terkait lainnya di situs ini. Jika ada kekurangan, silakan tinggalkan pesan untuk menunjukkannya. Terima kasih teman atas dukungan Anda untuk situs ini!