Multithreading. Das Java-Speichermodell (Teil 1)

Hallo Habr! Ich präsentiere Ihnen die Übersetzung des ersten Teils des Artikels "Java Memory Model" von Jakob Jenkov.



Ich mache eine Ausbildung in Java und musste den Artikel Java Memory Model studieren . Ich habe es zum besseren Verständnis übersetzt, aber damit das Gute nicht verloren geht, habe ich beschlossen, es mit der Community zu teilen. Ich denke, es wird für Anfänger nützlich sein, und wenn es jemandem gefällt, werde ich den Rest übersetzen.



Das ursprüngliche Java-Speichermodell war nicht sehr gut, daher wurde es in Java 1.5 überarbeitet. Diese Version wird heute noch verwendet (Java 14+).





Internes Java-Speichermodell



Das von der JVM intern verwendete Java-Speichermodell unterteilt den Speicher in einen Thread-Stapel und einen Heap. Dieses Diagramm zeigt das Java-Speichermodell aus logischer Sicht:



Bild



Jeder in der virtuellen Java-Maschine ausgeführte Thread verfügt über einen eigenen Stapel. Der Stapel enthält Informationen darüber, welche Methoden der Thread aufgerufen hat, um den aktuellen Ausführungspunkt zu erreichen. Ich werde dies als "Aufrufstapel" bezeichnen. Sobald der Thread seinen Code ausführt, ändert sich der Aufrufstapel.



Der Thread-Stapel enthält alle lokalen Variablen für jede ausgeführte Methode (alle Methoden im Aufrufstapel). Ein Thread kann nur auf seinen eigenen Stapel zugreifen. Lokale Variablen sind für alle anderen Threads außer dem Thread, der sie erstellt hat, unsichtbar. Selbst wenn zwei Threads denselben Code ausführen, erstellen sie dennoch lokale Variablen dieses Codes auf ihren eigenen Stapeln. Somit hat jeder Thread eine eigene Version jeder lokalen Variablen.



Alle lokalen Variablen primitiver Typen (Boolescher Wert, Byte, Kurzwert, Zeichen, Int, Long, Float, Double) werden vollständig im Thread-Stapel gespeichert und sind für andere Threads nicht sichtbar. Ein Thread kann eine Kopie einer primitiven Variablen an einen anderen Thread übergeben, jedoch keine primitive lokale Variable gemeinsam nutzen.



Der Heap enthält alle in Ihrer Java-Anwendung erstellten Objekte, unabhängig davon, welcher Thread das Objekt erstellt hat. Dies umfasst Versionen von Objekten primitiven Typs (z. B. Byte, Integer, Long usw.). Es spielt keine Rolle, ob das Objekt erstellt und einer lokalen Variablen zugewiesen oder als Mitgliedsvariable eines anderen Objekts erstellt wurde, es wird auf dem Heap gespeichert.



Das folgende Diagramm zeigt den Aufrufstapel und die lokalen Variablen, die auf Thread-Stapeln gespeichert sind, sowie die Objekte, die auf dem Heap gespeichert sind: Eine



Bild



lokale Variable kann vom primitiven Typ sein. In diesem Fall wird sie vollständig auf dem Thread-Stapel gespeichert.



Eine lokale Variable kann auch eine Objektreferenz sein. In diesem Fall wird die Referenz (lokale Variable) auf dem Thread-Stapel gespeichert, das Objekt selbst wird jedoch auf dem Heap gespeichert.



Ein Objekt kann Methoden enthalten, und diese Methoden können lokale Variablen enthalten. Diese lokalen Variablen werden auch auf dem Thread-Stapel gespeichert, selbst wenn das Objekt, dem die Methode gehört, auf dem Heap gespeichert ist.



Die Mitgliedsvariablen eines Objekts werden zusammen mit dem Objekt selbst auf dem Heap gespeichert. Dies gilt sowohl, wenn die Elementvariable vom primitiven Typ ist, als auch wenn es sich um eine Objektreferenz handelt.



Variablen einer statischen Klasse werden zusammen mit der Klassendefinition auch auf dem Heap gespeichert.



Auf Objekte auf dem Heap können alle Threads zugreifen, die einen Verweis auf das Objekt haben. Wenn ein Thread Zugriff auf ein Objekt hat, kann er auch auf die Elementvariablen dieses Objekts zugreifen. Wenn zwei Threads gleichzeitig eine Methode für dasselbe Objekt aufrufen, haben beide Zugriff auf die Mitgliedsvariablen des Objekts, aber jeder Thread verfügt über eine eigene Kopie der lokalen Variablen.



Hier ist ein Diagramm, das die obigen Punkte veranschaulicht:



Bild



Zwei Threads haben eine Reihe lokaler Variablen. Lokale Variable 2 zeigt auf ein freigegebenes Objekt auf dem Heap (Objekt 3). Das heißt, jeder der Threads hat eine eigene Kopie der lokalen Variablen mit einer eigenen Referenz. Somit verweisen zwei verschiedene Referenzen auf dasselbe Objekt auf dem Heap.



Beachten Sie, dass generisches Objekt 3 Verweise auf Objekt 2 und Objekt 4 als Elementvariablen enthält (durch Pfeile dargestellt). Über diese Links können zwei Threads auf Objekt 2 und Objekt 4 zugreifen.



Das Diagramm zeigt auch die lokale Variable (lokale Variable 1). Jede Kopie enthält unterschiedliche Verweise, die auf zwei verschiedene Objekte (Objekt 1 und Objekt 5) und nicht auf dasselbe verweisen. Theoretisch können beide Threads sowohl auf Objekt 1 als auch auf Objekt 5 zugreifen, wenn sie Verweise auf diese beiden Objekte haben. Im obigen Diagramm verweist jeder Thread jedoch nur auf eines der beiden Objekte.



Welche Art von Java-Code könnte das Ergebnis dieser Abbildungen sein? Nun, so einfacher Code wie der folgende Code:



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;
}


Die Methode run () ruft methodOne () auf und methodOne () ruft methodTwo () auf.



methodOne () deklariert eine primitive lokale Variable (localVariable1) vom Typ int und eine lokale Variable (localVariable2), die eine Objektreferenz ist.



Jeder Thread, der die One () -Methode ausführt, erstellt eine eigene Kopie von localVariable1 und localVariable2 auf ihren jeweiligen Stapeln. Die Variablen localVariable1 sind vollständig voneinander getrennt und befinden sich auf dem Stapel jedes Threads. Ein Thread kann nicht sehen, welche Änderungen ein anderer Thread an seiner Kopie von localVariable1 vornimmt.



Jeder Thread, der die One () -Methode ausführt, erstellt auch eine eigene Kopie von localVariable2. Zwei verschiedene Kopien von localVariable2 verweisen jedoch auf dasselbe Objekt auf dem Heap. Der Punkt ist, dass localVariable2 auf das Objekt zeigt, auf das die statische Variable sharedInstance verweist. Es gibt nur eine Kopie der statischen Variablen und diese Kopie wird auf dem Heap gespeichert. Daher verweisen beide Kopien von localVariable2 auf dieselbe MySharedObject-Instanz. Die MySharedObject-Instanz wird ebenfalls auf dem Heap gespeichert. Es entspricht Objekt 3 im obigen Diagramm.



Beachten Sie, dass die MySharedObject-Klasse auch zwei Elementvariablen enthält. Die Mitgliedsvariablen selbst werden zusammen mit dem Objekt auf dem Heap gespeichert. Die beiden Mitgliedsvariablen zeigen auf zwei andere Integer-Objekte. Diese ganzzahligen Objekte entsprechen Objekt 2 und Objekt 4 im Diagramm.



Beachten Sie auch, dass methodTwo () eine lokale Variable mit dem Namen localVariable1 erstellt. Diese lokale Variable ist eine Referenz auf ein Integer-Objekt. Die Methode setzt die Referenz localVariable1 so, dass sie auf eine neue Integer-Instanz verweist. Der Link wird für jeden Thread in einer eigenen Kopie von localVariable1 gespeichert. Die beiden Integer-Instanzen werden auf dem Heap gespeichert. Da die Methode bei jeder Ausführung ein neues Integer-Objekt erstellt, erstellen die beiden Threads, die diese Methode ausführen, separate Integer-Instanzen. Sie entsprechen Objekt 1 und Objekt 5 im obigen Diagramm.



Beachten Sie auch die beiden Elementvariablen in der MySharedObject-Klasse vom Typ long, einem primitiven Typ. Da diese Variablen Mitgliedsvariablen sind, werden sie zusammen mit dem Objekt auf dem Heap gespeichert. Auf dem Thread-Stack werden nur lokale Variablen gespeichert.



Teil 2 ist da.



All Articles