Banyak materi online menggambarkan model memori Java, yang akan memperkenalkan bahwa ada memori utama, dan setiap utas pekerja memiliki memori kerjanya sendiri. Akan ada satu bagian data dalam memori utama dan satu bagian dalam memori kerja. Akan ada berbagai operasi atom antara memori kerja dan memori utama untuk disinkronkan.
Gambar berikut berasal dari blog ini
Namun, karena evolusi kontinu dari versi Java, model memori juga telah berubah. Artikel ini hanya berbicara tentang beberapa fitur model memori Java. Apakah itu model memori baru atau model memori lama, itu akan terlihat lebih jelas setelah memahami fitur -fitur ini.
1. Atomisitas
Atomisitas berarti suatu operasi tidak terputus. Bahkan ketika beberapa utas dieksekusi bersama, setelah operasi dimulai, itu tidak akan terganggu oleh utas lain.
Secara umum diyakini bahwa instruksi CPU adalah operasi atom, tetapi kode yang kami tulis tidak selalu merupakan operasi atom.
Misalnya, i ++. Operasi ini bukan operasi atom, pada dasarnya dibagi menjadi 3 operasi, baca i, melakukan +1, dan menetapkan nilai untuk i.
Misalkan ada dua utas. Ketika utas pertama berbunyi i = 1, operasi +1 belum dilakukan, dan beralih ke utas kedua. Pada saat ini, utas kedua juga membaca i = 1. Kemudian kedua utas melakukan operasi +1 berikutnya dan kemudian menetapkan nilai kembali, saya bukan 3, tetapi 2. Jelas ada ketidakkonsistenan dalam data.
Misalnya, membaca nilai panjang 64-bit pada JVM 32-bit bukanlah operasi atom. Tentu saja, JVM 32-bit membaca bilangan bulat 32-bit sebagai operasi atom.
2. Pesanan
Selama konkurensi, pelaksanaan program mungkin rusak.
Ketika komputer menjalankan kode, itu tidak harus dijalankan dalam urutan program.
kelas orderexample {int a = 0; bendera boolean = false; Public void writer () {a = 1; bendera = true; } public void reader () {if (flag) {int i = a +1; }}} Misalnya, dalam kode di atas, dua metode dipanggil oleh masing -masing dua utas. Menurut akal sehat, utas penulisan harus terlebih dahulu menjalankan A = 1, dan kemudian menjalankan flag = true. Ketika utas baca membaca, i = 2;
Tetapi karena a = 1 dan flag = true, tidak ada korelasi logis. Oleh karena itu, dimungkinkan untuk membalikkan urutan eksekusi, dan dimungkinkan untuk mengeksekusi bendera = benar pertama, dan kemudian a = 1. Pada saat ini, ketika flag = true, beralih ke utas baca. Pada saat ini, A = 1 belum dieksekusi, maka utas baca akan i = 1.
Tentu saja ini tidak mutlak. Ada kemungkinan bahwa akan ada di luar urutan dan mungkin tidak terjadi.
Jadi mengapa ada di luar urutan? Ini dimulai dengan instruksi CPU. Setelah kode di Java dikompilasi, akhirnya dikonversi menjadi kode perakitan.
Eksekusi instruksi dapat dibagi menjadi banyak langkah. Dengan asumsi bahwa instruksi CPU dibagi menjadi langkah -langkah berikut
Misalkan ada dua instruksi di sini
Secara umum, kami akan berpikir bahwa instruksi dieksekusi secara serial, pertama -tama menjalankan instruksi 1, dan kemudian menjalankan instruksi 2. Dengan asumsi bahwa setiap langkah membutuhkan 1 periode waktu CPU, kemudian melaksanakan dua instruksi ini membutuhkan 10 periode waktu CPU, yang terlalu tidak efisien untuk melakukannya. Bahkan, instruksi dieksekusi secara paralel. Tentu saja, ketika instruksi pertama dijalankan jika, instruksi kedua tidak dapat melakukan jika karena instruksi mendaftar dan sejenisnya tidak dapat ditempati pada saat yang sama. Jadi seperti yang ditunjukkan pada gambar di atas, kedua instruksi dieksekusi secara paralel dengan cara yang relatif terhuyung -huyung. Ketika instruksi 1 menjalankan ID, instruksi 2 menjalankan IF. Dengan cara ini, dua instruksi dieksekusi hanya dalam 6 periode waktu CPU, yang relatif efisien.
Menurut ide ini, mari kita lihat bagaimana instruksi A = B+C dieksekusi.
Seperti yang ditunjukkan pada gambar, ada operasi idle (x) selama operasi ADD, karena ketika Anda ingin menambahkan B dan C, ketika operasi X Add dalam gambar, C belum membaca dari memori (C hanya membaca dari memori ketika operasi MEM selesai. Ada pertanyaan di sini. Pada saat ini, tidak ada yang dibaca dengan sirkuit, bagaimana R1 dapat ditambahkan oleh R1, bagaimana R1 dapat ditambahkan? Perangkat keras, jadi tidak perlu menunggu WB dieksekusi sebelum ADD dilakukan). Oleh karena itu, akan ada waktu idle (x) dalam operasi Tambahkan. Dalam operasi SW, karena instruksi ex tidak dapat dilakukan secara bersamaan dengan Instruksi Tambahan Ex, akan ada waktu yang menganggur (x).
Selanjutnya, mari kita berikan contoh yang sedikit lebih rumit
A = B+C.
D = EF
Instruksi yang sesuai adalah sebagai berikut
Alasannya mirip dengan di atas, jadi saya tidak akan menganalisisnya di sini. Kami menemukan bahwa ada banyak X di sini, dan ada banyak siklus waktu yang terbuang, dan kinerja juga terpengaruh. Apakah ada cara untuk mengurangi jumlah XS?
Kami berharap dapat menggunakan beberapa operasi untuk mengisi waktu luang X, karena ADD memiliki ketergantungan data dengan instruksi di atas, dan kami berharap dapat menggunakan beberapa instruksi tanpa ketergantungan data untuk mengisi waktu luang yang dihasilkan oleh ketergantungan data.
Kami mengubah urutan instruksi
Setelah mengubah urutan instruksi, X dihilangkan. Periode waktu berjalan secara keseluruhan juga menurun.
Instruksi Papan Pesan Dapat Membuat Pipeline lebih halus
Tentu saja, prinsip penataan ulang instruksi adalah bahwa ia tidak dapat menghancurkan semantik program serial. Sebagai contoh, a = 1, b = a+1, instruksi seperti itu tidak akan diatur ulang karena hasil serial dari penataan ulang berbeda dari yang asli.
Penataan ulang instruksi hanyalah cara untuk mengoptimalkan kompiler atau CPU, dan optimasi ini telah menyebabkan masalah dengan program di awal bab ini.
Bagaimana cara menyelesaikannya? Gunakan kata kunci yang mudah menguap, seri berikutnya ini akan diperkenalkan.
3. Visibilitas
Visibilitas mengacu pada apakah utas lain dapat segera mengetahui modifikasi ketika utas memodifikasi nilai variabel bersama.
Masalah visibilitas dapat muncul di berbagai tautan. Sebagai contoh, instruksi ulang instruksi yang baru saja disebutkan juga akan menyebabkan masalah visibilitas, dan di samping itu, optimalisasi kompiler atau optimalisasi perangkat keras tertentu juga akan menyebabkan masalah visibilitas.
Misalnya, utas mengoptimalkan nilai bersama ke dalam memori, sementara utas lain mengoptimalkan nilai bersama ke dalam cache. Saat memodifikasi nilai dalam memori, cache tidak tahu modifikasi.
Misalnya, beberapa optimisasi perangkat keras, ketika suatu program menulis beberapa kali ke alamat yang sama, itu akan berpikir itu tidak perlu dan hanya menyimpan tulisan terakhir, sehingga data yang ditulis sebelumnya tidak akan terlihat di utas lain.
Singkatnya, sebagian besar masalah dengan visibilitas berasal dari optimasi.
Selanjutnya, mari kita lihat masalah visibilitas yang timbul dari level mesin virtual Java
Masalahnya berasal dari blog
paket edu.hushi.jvm; /** * * @Author -10 * */Visibilitas kelas publik Extends Thread {Private Boolean Stop; public void run () {int i = 0; while (! Stop) {i ++; } System.out.println ("Finish loop, i =" + i); } public void stopit () {stop = true; } public boolean getStop () {return stop; } public static void main (string [] args) melempar Exception {visibilityTest v = new visibilityTest (); v.start (); Thread.sleep (1000); v.stopit (); Thread.sleep (2000); System.out.println ("Finish Main"); System.out.println (v.getStop ()); }} Kode ini sangat sederhana. Utas V menyimpan i ++ di loop while sampai utas utama memanggil metode berhenti, mengubah nilai variabel berhenti di utas V untuk menghentikan loop.
Masalah terjadi ketika kode yang tampaknya sederhana berjalan. Program ini dapat menghentikan utas dari melakukan operasi pendakian sendiri dalam mode klien, tetapi dalam mode server, itu akan menjadi loop tak terbatas terlebih dahulu. (Lebih banyak optimasi JVM dalam mode server)
Sebagian besar sistem 64-bit adalah mode server, dan dijalankan dalam mode server:
Selesai Utama
BENAR
Hanya dua kalimat ini yang akan dicetak, tetapi loop finish tidak akan dicetak. Tetapi Anda dapat menemukan bahwa nilai berhenti sudah benar.
Penulis blog ini menggunakan alat untuk mengembalikan program untuk perakitan kode
Hanya sebagian dari kode perakitan yang dicegat di sini, dan bagian merah adalah bagian loop. Dapat dilihat dengan jelas bahwa hanya 0x0193bf9d adalah verifikasi berhenti, sedangkan bagian merah tidak mengambil nilai berhenti, sehingga loop tak terbatas dilakukan.
Ini adalah hasil dari optimasi JVM. Bagaimana cara menghindarinya? Seperti Petunjuk Pesan, gunakan kata kunci yang mudah menguap.
Jika volatile ditambahkan, kembalikan kode perakitan dan Anda akan menemukan bahwa setiap loop akan mendapatkan nilai berhenti.
Selanjutnya, mari kita lihat beberapa contoh dalam "spesifikasi bahasa java"
Gambar di atas menunjukkan bahwa penyusunan ulang instruksi akan menghasilkan hasil yang berbeda.
Alasan mengapa R5 = R2 dibuat pada gambar di atas adalah bahwa R2 = R1.x, R5 = R1.x, dan secara langsung dioptimalkan ke R5 = R2 pada waktu kompilasi. Pada akhirnya, hasilnya berbeda.
4. Terjadi sebelum
5. Konsep keamanan utas
Ini mengacu pada fakta bahwa ketika fungsi atau perpustakaan fungsi tertentu dipanggil dalam lingkungan multi-utas, ia dapat dengan benar memproses variabel lokal dari setiap utas dan memungkinkan fungsi program diselesaikan dengan benar.
Misalnya, contoh i ++ yang disebutkan di awal
Ini akan menyebabkan benang tidak aman.
Untuk detail tentang keamanan utas, silakan merujuk ke blog ini yang saya tulis sebelumnya, atau ikuti seri berikutnya, dan Anda juga akan berbicara tentang konten terkait.