Pengenalan kepada Model Memori Java

Model Memori Java (JMM) menerangkan gelagat benang dalam persekitaran masa jalan Java. Model memori adalah sebahagian daripada semantik bahasa Java, dan menerangkan perkara yang boleh dan tidak sepatutnya dijangkakan oleh pengaturcara apabila membangunkan perisian bukan untuk mesin Java tertentu, tetapi untuk Java secara keseluruhan.

Model memori Java asal (yang, khususnya, merujuk kepada "memori percolocal"), yang dibangunkan pada tahun 1995, dianggap sebagai kegagalan: banyak pengoptimuman tidak boleh dibuat tanpa kehilangan jaminan keselamatan kod. Khususnya, terdapat beberapa pilihan untuk menulis "tunggal" berbilang benang:

  • sama ada setiap tindakan mengakses singleton (walaupun objek telah dicipta lama dahulu, dan tiada apa yang boleh berubah) akan menyebabkan kunci antara benang;
  • atau dalam satu set keadaan tertentu, sistem akan mengeluarkan penyendiri yang belum selesai;
  • atau dalam satu set keadaan tertentu, sistem akan mewujudkan dua penyendiri;
  • atau reka bentuk akan bergantung pada kelakuan mesin tertentu.

Oleh itu, mekanisme ingatan telah direka bentuk semula. Pada tahun 2005, dengan keluaran Java 5, pendekatan baru telah dibentangkan, yang telah dipertingkatkan lagi dengan keluaran Java 14.

Model baharu ini berdasarkan tiga peraturan:

Peraturan #1 : Program berbenang tunggal dijalankan secara pseudo-berjujukan. Ini bermakna: pada hakikatnya, pemproses boleh melakukan beberapa operasi setiap jam, pada masa yang sama menukar susunan mereka, bagaimanapun, semua kebergantungan data kekal, jadi tingkah laku tidak berbeza daripada berurutan.

Peraturan nombor 2 : tiada nilai entah dari mana. Membaca mana-mana pembolehubah (kecuali tidak meruap panjang dan berganda, yang mana peraturan ini mungkin tidak dipegang) akan mengembalikan sama ada nilai lalai (sifar) atau sesuatu yang ditulis di sana oleh arahan lain.

Dan peraturan nombor 3 : selebihnya acara dilaksanakan mengikut tertib, jika ia disambungkan oleh hubungan pesanan separa yang ketat "dilaksanakan sebelum" ( berlaku sebelum ).

Berlaku sebelum ini

Leslie Lamport tampil dengan konsep Happens sebelum ini . Ini ialah hubungan tertib separa yang ketat yang diperkenalkan antara perintah atom (++ dan -- bukan atom) dan tidak bermaksud "secara fizikal sebelum ini".

Ia mengatakan bahawa pasukan kedua akan "mengetahui" perubahan yang dibuat oleh yang pertama.

Berlaku sebelum ini

Sebagai contoh, satu dilaksanakan sebelum yang lain untuk operasi sedemikian:

Penyegerakan dan monitor:

  • Menangkap monitor ( kaedah kunci , mula disegerakkan) dan apa sahaja yang berlaku pada urutan yang sama selepasnya.
  • Pemulangan monitor ( buka kunci kaedah , akhir disegerakkan) dan apa sahaja yang berlaku pada urutan yang sama sebelum itu.
  • Mengembalikan monitor dan kemudian menangkapnya dengan benang lain.

Menulis dan membaca:

  • Menulis kepada mana-mana pembolehubah dan kemudian membacanya dalam aliran yang sama.
  • Semuanya dalam urutan yang sama sebelum menulis kepada pembolehubah yang tidak menentu, dan tulisan itu sendiri. bacaan yang tidak menentu dan segala-galanya pada urutan yang sama selepasnya.
  • Menulis kepada pembolehubah yang tidak menentu dan kemudian membacanya semula. Tulisan yang tidak menentu berinteraksi dengan memori dengan cara yang sama seperti pemulangan monitor, manakala bacaan adalah seperti tangkapan. Ternyata jika satu utas menulis kepada pembolehubah yang tidak menentu, dan yang kedua menemuinya, semua yang mendahului penulisan dilaksanakan sebelum semua yang datang selepas bacaan; lihat gambar.

Penyelenggaraan objek:

  • Permulaan statik dan sebarang tindakan dengan sebarang contoh objek.
  • Menulis ke medan akhir dalam pembina dan segala-galanya selepas pembina. Sebagai pengecualian, hubungan berlaku-sebelum tidak menyambung secara transitif kepada peraturan lain dan oleh itu boleh menyebabkan perlumbaan antara benang.
  • Sebarang kerja dengan objek dan finalize() .

Perkhidmatan strim:

  • Memulakan utas dan sebarang kod dalam utas.
  • Pembolehubah sifar yang berkaitan dengan utas dan sebarang kod dalam utas.
  • Kod dalam benang dan join() ; kod dalam benang dan isAlive() == false .
  • interrupt() benang dan mengesan bahawa ia telah berhenti.

Berlaku sebelum nuansa kerja

Mengeluarkan monitor berlaku sebelum berlaku sebelum memperoleh monitor yang sama. Perlu diingat bahawa ia adalah pelepasan, dan bukan pintu keluar, iaitu, anda tidak perlu risau tentang keselamatan apabila menggunakan tunggu.

Mari kita lihat bagaimana pengetahuan ini akan membantu kita membetulkan contoh kita. Dalam kes ini, segala-galanya adalah sangat mudah: hanya keluarkan semakan luaran dan biarkan penyegerakan seperti sedia ada. Sekarang benang kedua dijamin untuk melihat semua perubahan, kerana ia hanya akan mendapat monitor selepas benang lain mengeluarkannya. Dan kerana dia tidak akan melepaskannya sehingga semuanya dimulakan, kita akan melihat semua perubahan sekaligus, dan bukan secara berasingan:

public class Keeper {
    private Data data = null;

    public Data getData() {
        synchronized(this) {
            if(data == null) {
                data = new Data();
            }
        }

        return data;
    }
}

Menulis kepada pembolehubah yang tidak menentu berlaku-sebelum membaca daripada pembolehubah yang sama. Perubahan yang telah kami buat, sudah tentu, membetulkan pepijat, tetapi ia meletakkan sesiapa yang menulis kod asal kembali ke tempat asalnya - menyekat setiap kali. Kata kunci yang tidak menentu boleh menjimatkan. Sebenarnya, kenyataan yang dimaksudkan itu bermaksud apabila membaca semua yang diisytiharkan tidak menentu, kita akan sentiasa mendapat nilai sebenar.

Di samping itu, seperti yang saya katakan sebelum ini, untuk medan yang tidak menentu, penulisan sentiasa (termasuk panjang dan berganda) operasi atom. Satu lagi perkara penting: jika anda mempunyai entiti yang tidak menentu yang mempunyai rujukan kepada entiti lain (contohnya, tatasusunan, Senarai atau beberapa kelas lain), maka hanya rujukan kepada entiti itu sendiri akan sentiasa "segar", tetapi tidak kepada semua perkara dalam ia masuk.

Jadi, kembali kepada domba jantan pengunci dua kali kami. Menggunakan tidak menentu, anda boleh membetulkan keadaan seperti ini:

public class Keeper {
    private volatile Data data = null;

    public Data getData() {
        if(data == null) {
            synchronized(this) {
                if(data == null) {
                    data = new Data();
                }
            }
        }
        return data;
    }
}

Di sini kita masih mempunyai kunci, tetapi hanya jika data == null. Kami menapis kes yang selebihnya menggunakan bacaan yang tidak menentu. Ketepatan dipastikan oleh fakta bahawa stor yang tidak menentu berlaku-sebelum bacaan yang tidak menentu, dan semua operasi yang berlaku dalam pembina dapat dilihat oleh sesiapa sahaja yang membaca nilai medan.