Rumah  >  Artikel  >  Java  >  Cara menggunakan kata kunci yang tidak menentu dalam Java

Cara menggunakan kata kunci yang tidak menentu dalam Java

WBOY
WBOYke hadapan
2023-04-22 15:55:161236semak imbas

1. Konsep berkaitan model ingatan

Seperti yang kita sedia maklum, apabila komputer melaksanakan program, setiap arahan dilaksanakan dalam CPU, dan proses melaksanakan arahan tidak dapat tidak melibatkan pembacaan dan penulisan data. Memandangkan data sementara semasa menjalankan program disimpan dalam memori utama (memori fizikal), terdapat masalah Memandangkan CPU melaksanakan dengan sangat cepat, proses membaca data dari memori dan menulis data ke memori adalah berbeza daripada bahawa CPU Kelajuan melaksanakan arahan adalah lebih perlahan, jadi jika operasi data mesti dilakukan melalui interaksi dengan memori pada bila-bila masa, kelajuan pelaksanaan arahan akan dikurangkan dengan banyak. Jadi terdapat cache dalam CPU.

Iaitu, apabila program sedang berjalan, data yang diperlukan untuk operasi akan disalin dari memori utama ke cache CPU Kemudian CPU boleh terus membaca data dari cache dan menulis kepadanya apabila melakukan pengiraan , data dalam cache dimuat semula ke memori utama. Ambil contoh mudah, seperti kod berikut:

1i = i + 1;

Apabila benang melaksanakan pernyataan ini, ia akan membaca nilai i dari memori utama, dan kemudian menyalin salinan ke cache Kemudian CPU melaksanakan arahan untuk menambah i sebanyak 1, kemudian menulis data ke cache, dan akhirnya menulis nilai ke cache Nilai terkini i dalam cache dibuang ke memori utama.

Tiada masalah dengan kod ini berjalan dalam satu utas, tetapi akan ada masalah apabila berjalan dalam berbilang utas. Dalam CPU berbilang teras, setiap thread mungkin dijalankan dalam CPU yang berbeza, jadi setiap thread mempunyai cache sendiri apabila dijalankan (untuk CPU teras tunggal, masalah ini sebenarnya juga berlaku, tetapi ia dijadualkan mengikut thread. borang untuk dilaksanakan secara berasingan) . Dalam artikel ini, kami mengambil CPU berbilang teras sebagai contoh.

Sebagai contoh, dua utas sedang melaksanakan kod ini pada masa yang sama Jika nilai i ialah 0 pada mulanya, maka kami berharap nilai i akan menjadi 2 selepas pelaksanaan dua utas. Tetapi adakah ini akan berlaku?

Mungkin terdapat situasi berikut: pada mulanya, kedua-dua utas masing-masing membaca nilai i dan menyimpannya dalam cache CPU masing-masing, dan kemudian utas 1 menambah 1, dan kemudian menulis nilai terkini 1 i ke dalam memori. Pada masa ini, nilai i dalam cache thread 2 masih 0. Selepas menambah 1, nilai i ialah 1, dan kemudian thread 2 menulis nilai i ke dalam memori.

Nilai akhir i ialah 1, bukan 2. Ini adalah masalah konsistensi cache yang terkenal. Pembolehubah yang diakses oleh berbilang benang biasanya dipanggil pembolehubah kongsi.

Dalam erti kata lain, jika pembolehubah dicache dalam berbilang CPU (biasanya berlaku dalam pengaturcaraan berbilang benang), maka mungkin terdapat masalah ketidakkonsistenan cache.

Untuk menyelesaikan masalah ketidakkonsistenan cache, biasanya terdapat dua penyelesaian:

1) Dengan menambahkan LOCK# pada bas

2) Melalui protokol ketekalan cache

Kedua-dua kaedah ini disediakan di peringkat perkakasan.

Dalam CPU awal, masalah ketidakkonsistenan cache telah diselesaikan dengan menambahkan kunci LOCK# pada bas. Oleh kerana komunikasi antara CPU dan komponen lain dijalankan melalui bas, jika anda menambah LOCK# pada bas, ini bermakna CPU lain disekat daripada mengakses komponen lain (seperti memori), jadi hanya satu CPU boleh menggunakan ini. memori berubah-ubah. Sebagai contoh, dalam contoh di atas, jika utas sedang melaksanakan i = i +1, dan jika isyarat kunci LCOK# dihantar pada bas semasa pelaksanaan kod ini, maka CPU lain hanya boleh menunggu kod ini sepenuhnya Dilaksanakan Baca pembolehubah dari memori di mana pembolehubah i berada, dan kemudian lakukan operasi yang sepadan. Ini menyelesaikan masalah ketidakkonsistenan cache.

Walau bagaimanapun, terdapat masalah dengan kaedah di atas Semasa tempoh mengunci bas, CPU lain tidak dapat mengakses memori, menyebabkan kecekapan rendah.

Jadi protokol koheren cache muncul. Yang paling terkenal ialah protokol MESI Intel, yang memastikan salinan pembolehubah kongsi yang digunakan dalam setiap cache adalah konsisten. Idea terasnya ialah: apabila CPU menulis data, jika ia mendapati bahawa pembolehubah yang dikendalikan adalah pembolehubah yang dikongsi, iaitu, salinan pembolehubah juga wujud dalam CPU lain, isyarat akan dihantar untuk memberitahu CPU lain untuk menetapkan cache baris pembolehubah kepada keadaan tidak sah Oleh itu, apabila CPU lain perlu membaca pembolehubah ini dan mendapati bahawa baris cache yang menyimpan pembolehubah dalam cache mereka sendiri adalah tidak sah, maka ia akan membacanya semula dari memori.

2. Tiga konsep dalam pengaturcaraan serentak

Dalam pengaturcaraan serentak, kita biasanya menghadapi tiga masalah berikut: masalah atomicity, masalah keterlihatan dan masalah pesanan. Mari kita lihat ketiga-tiga konsep ini secara terperinci:

1.Atomicity

Atomicity: iaitu, satu operasi atau berbilang operasi sama ada dilaksanakan sepenuhnya dan proses pelaksanaan tidak akan diganggu oleh sebarang faktor, atau ia tidak dilaksanakan sama sekali.

Contoh yang sangat klasik ialah masalah pemindahan akaun bank:

Sebagai contoh, pemindahan 1,000 yuan daripada akaun A ke akaun B mesti termasuk dua operasi: menolak 1,000 yuan daripada akaun A dan menambah 1,000 yuan ke akaun B.

Bayangkan apa akibatnya jika kedua-dua operasi ini bukan atom. Katakan selepas menolak 1,000 yuan daripada akaun A, operasi itu ditamatkan secara tiba-tiba. Kemudian dia mengeluarkan 500 yuan daripada B. Selepas mengeluarkan 500 yuan, dia kemudian melakukan operasi menambah 1,000 yuan ke akaun B. Ini akan menyebabkan walaupun akaun A ditolak sebanyak 1,000 yuan, akaun B tidak menerima 1,000 yuan yang dipindahkan.

Oleh itu, kedua-dua operasi ini mestilah bersifat atom untuk memastikan tiada masalah yang tidak dijangka berlaku.

Apakah akibat daripada refleksi yang sama dalam pengaturcaraan serentak?

Untuk memberikan contoh paling mudah, fikirkan tentang apa yang akan berlaku jika proses penetapan kepada pembolehubah 32-bit bukan atom?

1i = 9;

Jika thread melaksanakan pernyataan ini, saya akan buat sementara waktu menganggap bahawa memberikan nilai kepada pembolehubah 32-bit melibatkan dua proses: memberikan nilai kepada 16 bit yang lebih rendah dan memberikan nilai kepada 16 bit atas.

Kemudian situasi mungkin berlaku: apabila nilai 16-bit rendah ditulis, ia tiba-tiba terganggu, dan pada masa ini benang lain membaca nilai i, maka data yang salah dibaca.

2. Keterlihatan

Keterlihatan bermakna apabila berbilang benang mengakses pembolehubah yang sama, jika satu utas mengubah suai nilai pembolehubah, utas lain boleh melihat nilai yang diubah suai dengan serta-merta.

Untuk contoh mudah, lihat kod berikut:

//Kod dilaksanakan oleh urutan 1

int i = 0;

i = 10;

//Kod dilaksanakan oleh urutan 2

j = i;

Jika thread 1 dilaksanakan oleh CPU1, thread 2 dilaksanakan oleh CPU2. Daripada analisis di atas, dapat dilihat bahawa apabila benang 1 melaksanakan ayat i = 10, ia mula-mula akan memuatkan nilai awal i ke dalam cache CPU1, dan kemudian menetapkannya kepada 10, kemudian nilai i dalam cache. daripada CPU1 menjadi 10 , tetapi ia tidak ditulis ke memori utama serta-merta.

Pada masa ini, thread 2 melaksanakan j = i Ia akan membaca nilai i dari memori utama dan memuatkannya ke dalam cache CPU2 Perhatikan bahawa nilai i dalam ingatan masih 0 pada masa ini jadikan nilai j = 0, dan Bukan 10.

Ini ialah masalah keterlihatan Selepas utas 1 mengubah suai pembolehubah i, utas 2 tidak segera melihat nilai yang diubah suai oleh utas 1.

3. Keteraturan

Keteraturan: Iaitu, susunan pelaksanaan program dilaksanakan mengikut susunan kod. Untuk contoh mudah, lihat kod berikut:

int i = 0;

bendera boolean = palsu;

i = 1; //Pernyataan 1

bendera = benar; //Pernyataan 2

Kod di atas mentakrifkan pembolehubah jenis int dan pembolehubah jenis boolean, dan kemudian memberikan nilai kepada dua pembolehubah masing-masing. Berdasarkan urutan kod, pernyataan 1 adalah sebelum pernyataan 2. Jadi apabila JVM benar-benar melaksanakan kod ini, adakah ia akan menjamin bahawa pernyataan 1 akan dilaksanakan sebelum pernyataan 2? Tidak semestinya, kenapa? Arahan Susun Semula mungkin berlaku di sini.

Mari kita terangkan apakah penyusunan semula arahan Secara umumnya, untuk meningkatkan kecekapan operasi program, pemproses boleh mengoptimumkan kod input Ia tidak menjamin bahawa susunan pelaksanaan setiap pernyataan dalam program adalah konsisten dengan susunan dalam kod , tetapi ia akan Memastikan bahawa hasil pelaksanaan akhir program adalah konsisten dengan hasil pelaksanaan berurutan kod.

Sebagai contoh, dalam kod di atas, sama ada pernyataan 1 atau pernyataan 2 dilaksanakan terlebih dahulu tidak memberi kesan kepada hasil program akhir Kemudian ada kemungkinan semasa proses pelaksanaan, pernyataan 2 dilaksanakan dahulu dan pernyataan 1 dilaksanakan kemudian.

Tetapi harus diingat bahawa walaupun pemproses akan menyusun semula arahan, ia akan memastikan bahawa hasil akhir program akan sama dengan hasil pelaksanaan berurutan bagi kod itu. Lihat contoh berikut:

int a = 10; //Pernyataan 1

int r = 2; //Pernyataan 2

a = a + 3; //Pernyataan 3

r = a*a; //Pernyataan 4

Kod ini mempunyai 4 pernyataan, jadi susunan pelaksanaan yang mungkin ialah:

Jadi mungkinkah ini adalah perintah pelaksanaan: Kenyataan 2 Kenyataan 1 Kenyataan 4 Kenyataan 3

Mustahil, kerana pemproses akan mempertimbangkan kebergantungan data antara arahan semasa menyusun semula Jika arahan, Arahan 2, mesti menggunakan hasil Arahan 1, maka pemproses akan memastikan Arahan 1 akan dilaksanakan sebelum Arahan 2.

Walaupun penyusunan semula tidak akan menjejaskan hasil pelaksanaan program dalam satu utas, bagaimana pula dengan multi-threading? Mari lihat contoh:

//Benang 1:

context = loadContext(); //Pernyataan 1

dimulakan = benar; //Pernyataan 2

//Benang 2:

sambil(!dimulakan ){

tidur()

}

doSomethingwithconfig(context);

Dalam kod di atas, memandangkan pernyataan 1 dan pernyataan 2 tidak mempunyai pergantungan data, ia mungkin disusun semula. Jika penyusunan semula berlaku, penyataan 2 dilaksanakan terlebih dahulu semasa pelaksanaan utas 1, dan utas 2 akan berfikir bahawa kerja permulaan telah selesai, maka ia akan melompat keluar daripada gelung sementara dan melaksanakan kaedah doSomethingwithconfig(context), tetapi pada kali ini konteks tidak wujud Apabila dimulakan akan menyebabkan ralat program.

Seperti yang dapat dilihat daripada di atas, penyusunan semula arahan tidak akan menjejaskan pelaksanaan satu utas, tetapi akan menjejaskan ketepatan pelaksanaan serentak utas.

Dalam erti kata lain, agar program serentak dapat dilaksanakan dengan betul, atomicity, keterlihatan dan keteraturan mesti dijamin. Selagi salah satu daripadanya tidak dijamin, ia boleh menyebabkan program berjalan dengan tidak betul.

3. Model memori Java

Terdahulu saya bercakap tentang beberapa masalah yang mungkin timbul dalam model memori dan pengaturcaraan serentak. Mari kita lihat model memori Java dan kaji apa yang menjamin model memori Java yang diberikan kepada kita dan kaedah serta mekanisme yang disediakan dalam Java untuk memastikan ketepatan pelaksanaan program semasa melaksanakan pengaturcaraan berbilang benang.

Spesifikasi Mesin Maya Java cuba mentakrifkan Model Memori Java (JMM) untuk melindungi perbezaan akses memori antara pelbagai platform perkakasan dan sistem pengendalian, supaya program Java boleh mencapai akses memori yang konsisten pada pelbagai platform. Jadi apakah yang ditetapkan oleh model memori Java? Ia mentakrifkan peraturan akses untuk pembolehubah dalam program. Ambil perhatian bahawa untuk mendapatkan prestasi pelaksanaan yang lebih baik, model memori Java tidak menyekat enjin pelaksanaan daripada menggunakan daftar pemproses atau cache untuk meningkatkan kelajuan pelaksanaan arahan, dan ia juga tidak menyekat pengkompil daripada menyusun semula arahan. Dalam erti kata lain, dalam model memori Java, terdapat juga isu ketekalan cache dan isu penyusunan semula arahan.

Model memori Java menetapkan bahawa semua pembolehubah disimpan dalam memori utama (serupa dengan memori fizikal yang dinyatakan sebelum ini), dan setiap thread mempunyai memori kerja sendiri (sama dengan cache sebelumnya). Semua operasi pada pembolehubah mengikut benang mesti dilakukan dalam memori kerja dan tidak boleh beroperasi secara langsung pada memori utama. Dan setiap benang tidak boleh mengakses memori kerja benang lain.

Untuk memberikan contoh mudah: dalam java, laksanakan pernyataan berikut:

1i = 10;

Benang pelaksanaan mesti terlebih dahulu menetapkan baris cache di mana pembolehubah i terletak dalam benang kerjanya sendiri, dan kemudian menulisnya ke memori utama. Daripada menulis nilai 10 terus ke dalam ingatan utama.

Jadi apakah jaminan yang disediakan oleh bahasa Java sendiri untuk atomicity, keterlihatan, dan susunan?

1.Atomicity

Di Java, membaca dan menetapkan operasi kepada pembolehubah jenis data asas ialah operasi atom, iaitu, operasi ini tidak boleh diganggu dan sama ada dilaksanakan atau tidak.

Walaupun ayat di atas kelihatan mudah, ia tidak begitu mudah untuk difahami. Lihat contoh berikut i:

Sila analisis yang mana antara operasi berikut adalah operasi atom:

x = 10; //Pernyataan 1

y = x; //Pernyataan 2

x++; //Pernyataan 3

x = x + 1; //Pernyataan 4

Pada pandangan pertama, sesetengah rakan mungkin mengatakan bahawa operasi dalam empat pernyataan di atas adalah semua operasi atom. Sebenarnya, hanya pernyataan 1 adalah operasi atom, dan tiga pernyataan lain bukan operasi atom.

Pernyataan 1 secara langsung memberikan nilai 10 kepada x, yang bermaksud bahawa utas yang melaksanakan pernyataan ini akan terus menulis nilai 10 ke dalam memori kerja.

Pernyataan 2 sebenarnya mengandungi dua operasi Ia mula-mula membaca nilai x, dan kemudian menulis nilai x ke memori kerja Walaupun dua operasi membaca nilai x dan menulis nilai x ke memori kerja adalah operasi Atom , tetapi bersama-sama mereka bukan operasi atom.

Begitu juga, x++ dan x = x+1 termasuk 3 operasi: membaca nilai x, menambah 1, dan menulis nilai baharu.

Oleh itu, antara empat pernyataan di atas, hanya operasi pernyataan 1 adalah atom.

Dalam erti kata lain, hanya bacaan dan tugasan mudah (dan nombor mesti diberikan kepada pembolehubah, tugasan bersama antara pembolehubah bukanlah operasi atom) adalah operasi atom.

Walau bagaimanapun, terdapat satu perkara yang perlu diperhatikan di sini: pada platform 32-bit, membaca dan menetapkan data 64-bit memerlukan dua operasi, dan keatomannya tidak dapat dijamin. Tetapi nampaknya dalam JDK terkini, JVM telah menjamin bahawa membaca dan memberikan data 64-bit juga merupakan operasi atom.

Seperti yang dapat dilihat daripada di atas, model memori Java hanya menjamin bahawa pembacaan dan penetapan asas adalah operasi atom Jika anda ingin mencapai keatoman untuk julat operasi yang lebih besar, anda boleh mencapainya melalui disegerakkan dan Kunci. Memandangkan disegerakkan dan Kunci boleh memastikan bahawa hanya satu utas melaksanakan blok kod pada bila-bila masa, tiada masalah atomicity, sekali gus memastikan atomicity.

2. Keterlihatan

Untuk keterlihatan, Java menyediakan kata kunci yang tidak menentu untuk memastikan keterlihatan.

Apabila pembolehubah yang dikongsi diubah suai tidak menentu, ia akan memastikan bahawa nilai yang diubah suai akan dikemas kini ke memori utama dengan serta-merta Apabila utas lain perlu membacanya, ia akan membaca nilai baharu daripada memori.

Pembolehubah kongsi biasa tidak dapat menjamin keterlihatan, kerana selepas pembolehubah kongsi biasa diubah suai, tidak pasti bila ia akan ditulis ke ingatan utama Apabila benang lain membacanya, nilai lama asal mungkin masih berada dalam ingatan pada masa ini, jadi Keterlihatan tidak dijamin.

Di samping itu, keterlihatan juga boleh dijamin melalui disegerakkan dan Kunci Disegerakkan dan Kunci boleh memastikan bahawa hanya satu utas memperoleh kunci dan melaksanakan kod penyegerakan pada masa yang sama, dan pengubahsuaian kepada pembolehubah dibuang ke memori utama sebelum melepaskannya. kunci. Oleh itu keterlihatan adalah terjamin.

3. Keteraturan

Dalam model memori Java, pengkompil dan pemproses dibenarkan untuk menyusun semula arahan, tetapi proses penyusunan semula tidak akan menjejaskan pelaksanaan atur cara berbenang tunggal, tetapi akan menjejaskan ketepatan pelaksanaan serentak berbilang benang.

Di Java, anda boleh menggunakan kata kunci yang tidak menentu untuk memastikan "ketertiban" tertentu (prinsip khusus diterangkan dalam bahagian seterusnya). Di samping itu, keteraturan boleh dipastikan melalui disegerakkan dan Kunci Jelas sekali, disegerakkan dan Kunci memastikan bahawa satu utas melaksanakan kod penyegerakan pada setiap saat, yang bersamaan dengan membiarkan utas melaksanakan kod penyegerakan secara berurutan, yang secara semula jadi memastikan keteraturan.

Di samping itu, model memori Java mempunyai beberapa "ketertiban" semula jadi, iaitu, keteraturan yang boleh dijamin tanpa sebarang cara Ini sering dipanggil prinsip berlaku-sebelum. Jika perintah pelaksanaan dua operasi tidak dapat disimpulkan daripada prinsip berlaku-sebelum, maka pesanan mereka tidak dijamin, dan mesin maya boleh menyusun semula mereka sesuka hati.

Mari perkenalkan prinsip berlaku-sebelum secara terperinci:

Peraturan jujukan program: Dalam urutan, mengikut susunan kod, operasi yang ditulis di hadapan berlaku sebelum operasi ditulis di belakang

Peraturan mengunci: Operasi buka kunci berlaku sebelum operasi kunci yang sama berlaku kemudian

Peraturan pembolehubah tidak menentu: operasi tulis kepada pembolehubah berlaku dahulu sebelum operasi baca seterusnya kepada pembolehubah

Peraturan peralihan: Jika operasi A berlaku sebelum operasi B, dan operasi B berlaku sebelum operasi C, maka boleh disimpulkan bahawa operasi A berlaku sebelum operasi C

Peraturan permulaan thread: Kaedah mula() objek Thread berlaku dahulu untuk setiap tindakan thread ini

Peraturan gangguan benang: Panggilan ke kaedah interrupt() benang berlaku dahulu apabila kod benang terputus mengesan kejadian peristiwa gangguan

Peraturan penamatan benang: Semua operasi dalam utas berlaku dahulu apabila utas ditamatkan Kami boleh mengesan bahawa utas telah ditamatkan melalui kaedah Thread.join() dan nilai pulangan Thread.isAlive()

Peraturan pemuktamadkan objek: Permulaan objek berlaku dahulu pada permulaan kaedah finalize()nya

8 prinsip ini dipetik daripada "Pemahaman Mendalam tentang Mesin Maya Java".

Antara 8 peraturan ini, 4 peraturan pertama adalah lebih penting, dan 4 peraturan terakhir adalah jelas.

Mari jelaskan 4 peraturan pertama:

Untuk peraturan pesanan program, pemahaman saya ialah pelaksanaan sekeping kod atur cara kelihatan teratur dalam satu utas. Ambil perhatian bahawa walaupun peraturan ini menyebut bahawa "operasi yang ditulis di hadapan berlaku sebelum operasi yang ditulis di belakang", ini bermakna bahawa susunan program nampaknya dilaksanakan adalah dalam susunan kod, kerana mesin maya mungkin berfungsi operasi pada kod program. Walaupun penyusunan semula dilakukan, hasil pelaksanaan akhir adalah konsisten dengan hasil pelaksanaan berurutan program Ia hanya akan menyusun semula arahan yang tidak mempunyai kebergantungan data. Oleh itu, dalam satu utas, pelaksanaan program nampaknya dilaksanakan mengikut urutan, yang harus difahami. Malah, peraturan ini digunakan untuk memastikan ketepatan pelaksanaan program menghasilkan satu utas, tetapi ia tidak dapat menjamin ketepatan pelaksanaan program dalam berbilang utas.

Peraturan kedua juga lebih mudah difahami Maksudnya, sama ada dalam satu utas atau berbilang benang, jika kunci yang sama dikunci, kunci mesti dilepaskan terlebih dahulu sebelum operasi kunci boleh diteruskan.

Peraturan ketiga adalah peraturan yang lebih penting dan akan menjadi tumpuan artikel berikut. Penjelasan intuitif ialah jika utas menulis pembolehubah dahulu, dan kemudian utas membacanya, maka operasi tulis pasti akan berlaku sebelum operasi baca.

Peraturan keempat sebenarnya mencerminkan sifat transitif prinsip berlaku-sebelum.

4. Analisis mendalam kata kunci yang tidak menentu

Saya telah bercakap tentang banyak perkara sebelum ini, tetapi semuanya membuka jalan untuk bercakap tentang kata kunci yang tidak menentu, jadi mari kita masuk ke topik seterusnya.

1.Dua tahap semantik kata kunci yang tidak menentu

Sebaik sahaja pembolehubah kongsi (pembolehubah ahli kelas, pembolehubah ahli statik kelas) diubah suai tidak menentu, ia mempunyai dua tahap semantik:

1) Memastikan keterlihatan apabila utas berbeza beroperasi pada pembolehubah ini, iaitu, jika satu utas mengubah suai nilai pembolehubah, nilai baharu itu dapat dilihat dengan serta-merta kepada utas lain.

2) Arahan pesanan semula adalah dilarang.

Mari lihat sekeping kod dahulu Jika utas 1 dilaksanakan dahulu dan utas 2 dilaksanakan kemudian:

//Benar 1

boolean stop = palsu;

sambil(!berhenti){

doSomething();

}

//Benang 2

berhenti = benar;

Kod ini ialah sekeping kod yang sangat tipikal, dan ramai orang mungkin menggunakan kaedah penandaan ini apabila mengganggu urutan. Tetapi sebenarnya, adakah kod ini akan berjalan dengan betul? Iaitu, adakah benang akan terganggu? Tidak semestinya, mungkin kebanyakan masa, kod ini boleh mengganggu benang, tetapi ia juga boleh menyebabkan benang tidak dapat diganggu (walaupun kemungkinan ini sangat kecil, tetapi apabila ini berlaku, ia akan menyebabkan gelung yang tidak terhingga).

Mari jelaskan sebab kod ini boleh menyebabkan urutan tidak dapat diganggu. Seperti yang dijelaskan sebelum ini, setiap utas mempunyai ingatan kerjanya sendiri semasa berjalan, jadi apabila utas 1 berjalan, ia akan menyalin nilai pembolehubah henti dan meletakkannya dalam memori kerjanya sendiri.

Kemudian apabila benang 2 menukar nilai pembolehubah henti, tetapi sebelum ia mempunyai masa untuk menulisnya ke dalam memori utama, utas 2 bertukar untuk melakukan perkara lain Kemudian utas 1 tidak mengetahui perubahan pembolehubah berhenti oleh utas 2, jadi ia akan terus bergelung ke bawah.

Tetapi selepas menggunakan pengubahsuaian yang tidak menentu, ia menjadi berbeza:

Pertama: Menggunakan kata kunci yang tidak menentu akan memaksa nilai yang diubah suai ditulis ke memori utama dengan segera;

Kedua: Jika kata kunci yang tidak menentu digunakan, apabila utas 2 membuat pengubahsuaian, ia akan menyebabkan barisan cache pembolehubah cache berhenti dalam memori kerja utas 1 menjadi tidak sah (jika ditunjukkan pada lapisan perkakasan, ia adalah cache yang sepadan baris dalam cache L1 atau L2 CPU) Tidak sah); Ketiga: Memandangkan baris cache bagi hentian pembolehubah cache dalam memori kerja utas 1 adalah tidak sah, utas 1 akan pergi ke memori utama untuk membaca nilai hentian pembolehubah sekali lagi.

Kemudian apabila benang 2 mengubah suai nilai henti (sudah tentu ini termasuk dua operasi, mengubah suai nilai dalam memori kerja benang 2, dan kemudian menulis nilai yang diubah suai ke dalam ingatan), baris cache hentian berubah akan dicache dalam ingatan kerja utas 1. Tidak sah, kemudian apabila utas 1 dibaca, ia mendapati bahawa baris cachenya tidak sah Ia akan menunggu alamat memori utama yang sepadan dengan baris cache dikemas kini, dan kemudian pergi ke memori utama yang sepadan untuk membaca. nilai terkini.

Kemudian apa yang dibaca oleh benang 1 ialah nilai betul terkini.

2. Adakah tidak menentu menjamin atomicity?

Daripada perkara di atas, kita tahu bahawa kata kunci yang tidak menentu menjamin keterlihatan operasi, tetapi bolehkah tidak menentu menjamin bahawa operasi pembolehubah adalah atom?

Mari lihat contoh:

Ujian kelas awam {

public volatile int inc = 0;

peningkatan kekosongan awam() {

inc++;

}

public static void main(String[] args) {

ujian Ujian akhir = Ujian baharu();

untuk(int i=0;i<10;i++){

Benang baharu(){

public void run() {

untuk(int j=0;j<1000;j++)

test.increase();

};

}.start();

}

while(Thread.activeCount()>1) //Pastikan semua urutan sebelumnya telah dilaksanakan

Thread.yield();

System.out.println(test.inc);

}

}

Fikirkanlah, apakah output program ini? Mungkin sesetengah kawan fikir ia adalah 10,000. Tetapi sebenarnya, apabila anda menjalankannya, anda akan mendapati bahawa keputusan adalah tidak konsisten setiap kali, dan mereka sentiasa nombor kurang daripada 10,000.

Sesetengah rakan mungkin mempunyai soalan, tidak, di atas adalah operasi kenaikan automatik pada inc pembolehubah Memandangkan tidak menentu menjamin keterlihatan, selepas menambah inc dalam setiap utas, ia boleh dilihat dalam urutan lain, jadi 10 utas melakukan 1000 operasi masing-masing, maka nilai akhir inc hendaklah 1000*10=10000.

Terdapat salah faham di sini Kata kunci yang tidak menentu boleh memastikan keterlihatan adalah betul, tetapi ralat dalam atur cara di atas ialah ia gagal menjamin atomicity. Keterlihatan hanya boleh memastikan bahawa nilai terkini dibaca setiap kali, tetapi tidak menentu tidak dapat menjamin keatoman operasi pada pembolehubah.

Seperti yang dinyatakan sebelum ini, operasi kenaikan automatik bukan atom Ia termasuk membaca nilai asal pembolehubah, menambah 1, dan menulis pada memori kerja. Ini bermakna bahawa tiga sub-operasi operasi kenaikan automatik boleh dilaksanakan secara berasingan, yang mungkin membawa kepada situasi berikut:

Jika nilai pembolehubah inc ialah 10 pada masa tertentu,

Urutan 1 melakukan operasi kenaikan automatik pada pembolehubah 1 mula-mula membaca nilai asal pembolehubah inc, dan kemudian urutan 1 disekat; Kemudian thread 2 melakukan operasi auto-increment pada pembolehubah, dan thread 2 juga membaca nilai asal pembolehubah inc Memandangkan thread 1 hanya membaca pembolehubah inc dan tidak mengubah suai pembolehubah, ia tidak akan menyebabkan kerja thread 2. . Baris cache pembolehubah cache inc dalam memori adalah tidak sah, jadi thread 2 akan terus ke memori utama untuk membaca nilai inc. Ia mendapati bahawa nilai inc ialah 10, kemudian menambah 1, menulis 11 ingatan bekerja, dan akhirnya menulisnya ke ingatan utama.

Kemudian benang 1 kemudian menambah 1. Oleh kerana nilai inc telah dibaca, ambil perhatian bahawa nilai inc dalam memori kerja benang 1 masih 10 pada masa ini, jadi nilai inc selepas benang 1 menambah 1 kepada inc ialah 11 . , kemudian tulis 11 ke memori kerja, dan akhirnya ke ingatan utama.

Kemudian selepas dua utas masing-masing melakukan operasi kenaikan automatik, inc hanya meningkat sebanyak 1.

Selepas menjelaskan perkara ini, sesetengah rakan mungkin mempunyai soalan Tidak, bukankah ia dijamin bahawa apabila pembolehubah diubah suai kepada pembolehubah yang tidak menentu, baris cache akan menjadi tidak sah? Kemudian benang lain akan membaca nilai baharu, ya, ini betul. Ini ialah peraturan pembolehubah tidak menentu dalam peraturan berlaku-sebelum di atas, tetapi perlu diingat bahawa selepas utas 1 membaca pembolehubah dan disekat, nilai inc tidak diubah suai. Kemudian walaupun tidak menentu boleh memastikan bahawa utas 2 membaca nilai pembolehubah inc dari memori, utas 1 tidak mengubah suainya, jadi utas 2 tidak akan melihat nilai yang diubah suai sama sekali.

Puncanya di sini Operasi kenaikan automatik bukan operasi atom, dan tidak menentu tidak dapat menjamin bahawa sebarang operasi pada pembolehubah adalah atom.

Menukar kod di atas kepada mana-mana yang berikut boleh mencapai kesan:

Menggunakan disegerakkan:

Ujian kelas awam {

public int inc = 0;

peningkatan kekosongan segerak awam() {

inc++;

}

public static void main(String[] args) {

ujian Ujian akhir = Ujian baharu();

untuk(int i=0;i<10;i++){

Benang baharu(){

public void run() {

untuk(int j=0;j<1000;j++)

test.increase();

};

}.start();

}

while(Thread.activeCount()>1) //Pastikan semua urutan sebelumnya telah dilaksanakan

Thread.yield();

System.out.println(test.inc);

}

}

Lihat Kod

采用Kunci:

Ujian kelas awam {

public int inc = 0;

Kunci kunci = ReentrantLock();

peningkatan kekosongan awam() {

lock.lock();

cuba {

inc++;

} akhirnya{

lock.unlock();

}

}

public static void main(String[] args) {

ujian Ujian akhir = Ujian baharu();

untuk(int i=0;i<10;i++){

Benang baharu(){

public void run() {

untuk(int j=0;j<1000;j++)

test.increase();

};

}.start();

}

while(Thread.activeCount()>1) //保证前面的线程都执行完

Thread.yield();

System.out.println(test.inc);

}

}

Lihat Kod

采用AtomicInteger:

Ujian kelas awam {

public AtomicInteger inc = new AtomicInteger();

peningkatan kekosongan awam() {

inc.getAndIncrement();

}

public static void main(String[] args) {

ujian Ujian akhir = Ujian baharu();

untuk(int i=0;i<10;i++){

Benang baharu(){

public void run() {

untuk(int j=0;j<1000;j++)

test.increase();

};

}.start();

}

while(Thread.activeCount()>1) //保证前面的线程都执行完

Thread.yield();

System.out.println(test.inc);

}

}

Lihat Kod

在java 1.5的java.util.concurrent.atomic包下提供了一些原子操作类,即对基本数据类型的 自增(些原子操作类,即对基本数据类型的 自增(加1月加1 )、以及加法操作(加一个数) ,减法操作(减一个数)进行了封装,保证这些操作是原子性操作。atomic是刐法甎家的(Banding Dan Tukar),CAS实际上是利用处理器提供的CMPXCHG指令实现的,而处理器执行CMPXCHG指令是一个原子性操作。

3. tidak menentu能保证有序性吗?

在前面提到 volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有度上保证有度上保证有序控。 tidak menentu关键字禁止指令重排序有两层意思:

1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定岨現肯定岨物肯定岨珲已经对后面的操作可见;在其后面的操作肯定还没有进行;

2)在进行指令优化时,不能将在对 volatile变量访问的语句放在其后面执行,也不能行,也不能放到其前面执行。

可能上面说的比较绕,举个简单的例子:

//x、y为非volatile变量

//flag为volatile变量

x = 2; //语句1

y = 0; //语句2

bendera = benar; //语句3

x = 4; //语句4

y = -1; //语句5

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到句3放到从语不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。

并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且可语的,且可语句2必定是执行完毕了的,且可语果对语句3、语句4、语句5是可见的。

那么我们回到前面举的一个例子:

//线程1:

konteks = loadContext(); //语句1

dimulakan = benar; //语句2

//线程2:

sambil(!dimulakan ){

tidur()

}

doSomethingwithconfig(context);

前面举这个例子的时候,提到有可能语句2会在语句1之前执行,那么久可能家导语句而线程2中就使用未初始化的context去进行操作,导致程序出错。

这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了,因为当执行,就不会出现这种问题了,因为当执行,当执行保证context已经初始化完毕。

4. tidak menentu的原理和实现机制

前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证有何保证有他排序的。

下面这段话摘自《深入理解Java虚拟机》:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关所生成的汇编代码发现,加入volatile关无关亮他们了缀指令”

kunci前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供一会一供3 1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也上不伎内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2)它会强制将对缓存的修改操作立即写入主存;

3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

五.使用volatile关键字的场景

Kata kunci yang disegerakkan menghalang berbilang utas daripada melaksanakan sekeping kod pada masa yang sama, yang akan mempengaruhi kecekapan pelaksanaan program Kata kunci yang tidak menentu mempunyai prestasi yang lebih baik daripada yang disegerakkan dalam beberapa kes, tetapi perlu diperhatikan bahawa kata kunci yang tidak menentu tidak boleh menggantikan yang disegerakkan. kata kunci, kerana kata kunci yang tidak menentu tidak dapat menjamin keatoman operasi. Secara umumnya, dua syarat berikut mesti dipenuhi untuk menggunakan tidak menentu:

1) Operasi menulis kepada pembolehubah tidak bergantung pada nilai semasa

2) Pembolehubah tidak termasuk dalam invarian dengan pembolehubah lain

Sebenarnya, syarat ini menunjukkan bahawa nilai sah yang boleh ditulis kepada pembolehubah tidak menentu adalah bebas daripada mana-mana keadaan program, termasuk keadaan semasa pembolehubah.

Sebenarnya, pemahaman saya ialah dua syarat di atas perlu memastikan bahawa operasi itu adalah operasi atom untuk memastikan program yang menggunakan kata kunci yang tidak menentu dapat dilaksanakan dengan betul semasa concurrency.

Berikut ialah beberapa senario di mana tidak menentu digunakan di Jawa.

1. Jumlah markah status

bendera boolean yang tidak menentu = palsu;

manakala(!bendera){

doSomething();

}

public void setFlag() {

bendera = benar;

}

boolean yang tidak menentu dimulakan = palsu;

//Benang 1:

context = loadContext();

dimulakan = benar;

//Benang 2:

sambil(!dimulakan ){

tidur()

}

doSomethingwithconfig(context);

2.semakan dua kali

kelas Singleton{

contoh Singleton statik meruap peribadi = batal;

Persendirian Singleton() {

}

awam statik Singleton getInstance() {

if(instance==null) {

disegerakkan (Singleton.class) {

if(instance==null)

instance = new Singleton();

}

}

contoh pulangan;

}

}

Atas ialah kandungan terperinci Cara menggunakan kata kunci yang tidak menentu dalam Java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam