Multithreading. The Java Memory Model (Bagian 1)

Halo, Habr! Saya sajikan untuk perhatian Anda terjemahan dari bagian pertama dari artikel "Java Memory Model" oleh Jakob Jenkov.



Saya akan menjalani pelatihan di Jawa dan perlu mempelajari artikel Java Memory Model . Saya menerjemahkannya untuk pemahaman yang lebih baik, tetapi agar kebaikannya tidak hilang, saya memutuskan untuk membagikannya kepada masyarakat. Saya pikir ini akan berguna untuk pemula, dan jika seseorang menyukainya, saya akan menerjemahkan sisanya.



Model memori Java asli tidak terlalu baik, sehingga direvisi di Java 1.5. Versi ini masih digunakan sampai sekarang (Java 14+).





Model memori Java internal



Model memori Java yang digunakan secara internal oleh JVM membagi memori menjadi tumpukan benang dan tumpukan. Diagram ini menggambarkan model memori Java dari sudut pandang logis:



gambar



Setiap thread yang berjalan di mesin virtual Java memiliki stack sendiri. Tumpukan berisi informasi tentang metode mana yang dipanggil utas untuk mencapai titik eksekusi saat ini. Saya akan menyebutnya sebagai "tumpukan panggilan". Segera setelah utas mengeksekusi kodenya, tumpukan panggilan berubah.



Tumpukan utas berisi semua variabel lokal untuk setiap metode yang dieksekusi (semua metode dalam tumpukan panggilan). Utas hanya dapat mengakses tumpukannya sendiri. Variabel lokal tidak terlihat oleh semua utas lain kecuali utas yang membuatnya. Bahkan jika dua utas mengeksekusi kode yang sama, mereka masih akan membuat variabel lokal dari kode itu di tumpukan mereka sendiri. Dengan demikian, setiap utas memiliki versi masing-masing variabel lokal.



Semua variabel lokal tipe primitif (boolean, byte, pendek, char, int, panjang, float, dobel) sepenuhnya disimpan pada tumpukan ulir dan tidak terlihat oleh utas lainnya. Satu utas dapat meneruskan salinan variabel primitif ke utas lain, tetapi tidak dapat membagikan variabel lokal primitif.



Tumpukan berisi semua objek yang dibuat dalam aplikasi Java Anda, terlepas dari utas mana yang membuat objek. Ini termasuk versi objek tipe primitif (misalnya Byte, Integer, Long, dll.). Tidak masalah jika objek itu dibuat dan ditugaskan ke variabel lokal atau dibuat sebagai variabel anggota objek lain, itu disimpan di heap.



Berikut adalah diagram yang menggambarkan stack panggilan dan variabel lokal yang disimpan di tumpukan thread, serta objek yang disimpan di heap:



gambar



Variabel lokal bisa dari tipe primitif, dalam hal ini sepenuhnya disimpan di tumpukan thread.



Variabel lokal juga bisa menjadi referensi objek. Dalam hal ini, referensi (variabel lokal) disimpan di tumpukan thread, tetapi objek itu sendiri disimpan di heap.



Objek dapat berisi metode, dan metode ini dapat berisi variabel lokal. Variabel lokal ini juga disimpan di tumpukan thread, bahkan jika objek yang memiliki metode disimpan di heap.



Variabel anggota suatu objek disimpan di heap bersama dengan objek itu sendiri. Ini benar baik ketika variabel anggota adalah tipe primitif dan ketika itu adalah referensi objek.



Variabel kelas statis juga disimpan di heap bersama dengan definisi kelas.



Objek di heap dapat diakses oleh semua utas yang memiliki referensi ke objek. Ketika sebuah thread memiliki akses ke objek, itu juga dapat mengakses variabel anggota objek itu. Jika dua utas memanggil metode pada objek yang sama pada saat yang sama, keduanya akan memiliki akses ke variabel anggota objek, tetapi setiap utas akan memiliki salinan variabel lokal sendiri.



Berikut adalah diagram yang menggambarkan poin-poin di atas:



gambar



Dua utas memiliki satu set variabel lokal. Variabel Lokal 2 menunjuk ke objek bersama di heap (Obyek 3). Yaitu, masing-masing utas memiliki salinan variabel lokal sendiri dengan referensi sendiri. Dengan demikian, dua referensi berbeda menunjuk ke objek yang sama di heap.



Perhatikan bahwa Objek 3 generik memiliki referensi ke Objek 2 dan Objek 4 sebagai variabel anggota (ditunjukkan oleh panah). Melalui tautan ini, dua utas dapat mengakses Objek 2 dan Objek 4.



Diagram juga menunjukkan variabel lokal (Variabel lokal 1). Setiap salinannya berisi referensi yang berbeda, yang menunjuk ke dua objek yang berbeda (Obyek 1 dan Objek 5), dan tidak ke yang sama. Secara teori, kedua utas tersebut dapat mengakses Objek 1 dan Objek 5 jika keduanya memiliki referensi ke kedua objek ini. Namun dalam diagram di atas, setiap utas hanya mereferensikan satu dari dua objek.



Jadi kode Java macam apa yang mungkin dihasilkan dari ilustrasi ini? Nah, sesederhana kode di bawah ini:



Public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        int localVariable1 = 45;

        MySharedObject localVariable2 =
            MySharedObject.sharedInstance;

        //... do more with local variables.

        methodTwo();
    }

    public void methodTwo() {
        Integer localVariable1 = new Integer(99);

        //... do more with local variable.
    }
}


public class MySharedObject {

    // ,    MySharedObject

    public static final MySharedObject sharedInstance =
        new MySharedObject();


    // -,      

    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);

    public long member1 = 12345;
    public long member2 = 67890;
}


Metode run () memanggil methodOne () dan methodOne () memanggil methodTwo ().



methodOne () mendeklarasikan variabel lokal primitif (localVariable1) dari tipe int dan variabel lokal (localVariable2) yang merupakan referensi objek.



Setiap utas yang menjalankan metode One () akan membuat salinan localVariable1 dan localVariable2 di masing-masing tumpukan. Variabel localVariable1 akan sepenuhnya terpisah satu sama lain, berada di tumpukan setiap utas. Satu utas tidak dapat melihat perubahan apa yang dilakukan utas lain terhadap salinan localVariable1.



Setiap utas yang menjalankan metode One () juga membuat salinan localVariable2 sendiri. Namun, dua salinan berbeda dari localVariable2 akhirnya menunjuk ke objek yang sama di heap. Intinya adalah bahwa localVariable2 menunjuk ke objek yang dirujuk oleh variabel statis sharedInstance. Hanya ada satu salinan variabel statis dan salinan itu disimpan di heap. Dengan demikian, kedua salinan localVariable2 akhirnya menunjuk ke contoh MySharedObject yang sama. Contoh MySharedObject juga disimpan di heap. Ini sesuai dengan Objek 3 pada diagram di atas.



Perhatikan bahwa kelas MySharedObject juga berisi dua variabel anggota. Variabel anggota itu sendiri disimpan di heap bersama dengan objek. Dua variabel anggota menunjuk ke dua objek Integer lainnya. Objek integer ini sesuai dengan Objek 2 dan Objek 4 dalam diagram.



Perhatikan juga bahwa methodTwo () membuat variabel lokal bernama localVariable1. Variabel lokal ini adalah referensi ke objek Integer. Metode ini menetapkan referensi localVariable1 untuk menunjuk ke contoh Integer baru. Tautan akan disimpan dalam salinan localVariable1 untuk masing-masing utas. Dua instance Integer akan disimpan di heap, dan karena metode membuat objek Integer baru setiap kali dieksekusi, dua thread yang mengeksekusi metode ini akan membuat instance Integer yang terpisah. Mereka sesuai dengan Obyek 1 dan Objek 5 pada diagram di atas.



Perhatikan juga dua variabel anggota dalam kelas tipe panjang MySharedObject, yang merupakan tipe primitif. Karena variabel ini adalah variabel anggota, mereka masih disimpan di heap bersama dengan objek. Hanya variabel lokal yang disimpan di tumpukan thread.



Bagian 2 ada di sini.



All Articles