Multithreading. Le modèle de mémoire Java (partie 1)

Bonjour, Habr! Je présente à votre attention la traduction de la première partie de l'article "Java Memory Model" de Jakob Jenkov.



Je suis en cours de formation en Java et j'avais besoin d'étudier l'article Modèle de mémoire Java . Je l'ai traduit pour une meilleure compréhension, mais pour que le bien ne soit pas perdu, j'ai décidé de le partager avec la communauté. Je pense que ce sera utile pour les débutants, et si quelqu'un l'aime, je traduirai le reste.



Le modèle de mémoire Java d'origine n'était pas très bon, il a donc été révisé en Java 1.5. Cette version est toujours utilisée aujourd'hui (Java 14+).





Modèle de mémoire Java interne



Le modèle de mémoire Java utilisé en interne par la JVM divise la mémoire en une pile de threads et un tas. Ce diagramme illustre le modèle de mémoire Java d'un point de vue logique:



image



chaque thread exécuté dans une machine virtuelle Java possède sa propre pile. La pile contient des informations sur les méthodes appelées par le thread pour atteindre le point d'exécution actuel. J'appellerai cela la "pile d'appels". Dès que le thread exécute son code, la pile d'appels change.



La pile de threads contient toutes les variables locales pour chaque méthode exécutée (toutes les méthodes de la pile d'appels). Un thread ne peut accéder qu'à sa propre pile. Les variables locales sont invisibles pour tous les autres threads à l'exception du thread qui les a créés. Même si deux threads exécutent le même code, ils créeront toujours des variables locales de ce code sur leurs propres piles. Ainsi, chaque thread a sa propre version de chaque variable locale.



Toutes les variables locales de types primitifs (boolean, byte, short, char, int, long, float, double) sont complètement stockées sur la pile de threads et ne sont pas visibles pour les autres threads. Un thread peut transmettre une copie d'une variable primitive à un autre thread, mais ne peut pas partager une variable locale primitive.



Le tas contient tous les objets créés dans votre application Java, quel que soit le thread qui a créé l'objet. Cela inclut les versions d'objets de types primitifs (par exemple Byte, Integer, Long, etc.). Peu importe si l'objet a été créé et affecté à une variable locale ou créé en tant que variable membre d'un autre objet, il est stocké sur le tas.



Voici un diagramme illustrant la pile d'appels et les variables locales qui sont stockées sur les piles de threads, ainsi que les objets qui sont stockés sur le tas: Une



image



variable locale peut être de type primitif, auquel cas elle est complètement stockée sur la pile du thread.



Une variable locale peut également être une référence d'objet. Dans ce cas, la référence (variable locale) est stockée sur la pile de threads, mais l'objet lui-même est stocké sur le tas.



Un objet peut contenir des méthodes et ces méthodes peuvent contenir des variables locales. Ces variables locales sont également stockées sur la pile de threads, même si l'objet qui possède la méthode est stocké sur le tas.



Les variables membres d'un objet sont stockées sur le tas avec l'objet lui-même. Cela est vrai à la fois lorsque la variable membre est de type primitif et lorsqu'il s'agit d'une référence d'objet.



Les variables d'une classe statique sont également stockées sur le tas avec la définition de classe.



Les objets sur le tas sont accessibles par tous les threads qui ont une référence à l'objet. Lorsqu'un thread a accès à un objet, il peut également accéder aux variables membres de cet objet. Si deux threads appellent une méthode sur le même objet en même temps, ils auront tous les deux accès aux variables membres de l'objet, mais chaque thread aura sa propre copie des variables locales.



Voici un diagramme illustrant les points ci-dessus:



image



Deux threads ont un ensemble de variables locales. La variable locale 2 pointe vers un objet partagé sur le tas (objet 3). Autrement dit, chacun des threads a sa propre copie de la variable locale avec sa propre référence. Ainsi, deux références différentes pointent vers le même objet sur le tas.



Notez que l'objet générique 3 a des références à l'objet 2 et à l'objet 4 en tant que variables membres (indiquées par des flèches). Grâce à ces liens, deux threads peuvent accéder à l'objet 2 et à l'objet 4.



Le diagramme montre également la variable locale (variable locale 1). Chaque copie contient des références différentes, qui pointent vers deux objets différents (Objet 1 et Objet 5) et non vers le même. En théorie, les deux threads peuvent accéder à la fois à l'objet 1 et à l'objet 5 s'ils ont des références à ces deux objets. Mais dans le diagramme ci-dessus, chaque thread ne fait référence qu'à l'un des deux objets.



Alors, quel type de code Java pourrait être le résultat de ces illustrations? Eh bien, un code aussi simple que le code ci-dessous:



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


La méthode run () appelle methodOne () et methodOne () appelle methodTwo ().



methodOne () déclare une variable locale primitive (localVariable1) de type int et une variable locale (localVariable2) qui est une référence d'objet.



Chaque thread qui exécute la méthode One () créera sa propre copie de localVariable1 et localVariable2 sur leurs piles respectives. Les variables localVariable1 seront complètement séparées les unes des autres, étant sur la pile de chaque thread. Un thread ne peut pas voir les modifications qu'un autre thread apporte à sa copie de localVariable1.



Chaque thread qui exécute la méthode One () crée également sa propre copie de localVariable2. Cependant, deux copies différentes de localVariable2 finissent par pointer vers le même objet sur le tas. Le fait est que localVariable2 pointe vers l'objet référencé par la variable statique sharedInstance. Il n'y a qu'une seule copie de la variable statique et cette copie est stockée sur le tas. Ainsi, les deux copies de localVariable2 pointent vers la même instance MySharedObject. L'instance MySharedObject est également stockée sur le tas. Il correspond à l'objet 3 du schéma ci-dessus.



Notez que la classe MySharedObject contient également deux variables membres. Les variables membres elles-mêmes sont stockées sur le tas avec l'objet. Les deux variables membres pointent vers deux autres objets Integer. Ces objets entiers correspondent aux objets 2 et 4 du diagramme.



Notez également que methodTwo () crée une variable locale nommée localVariable1. Cette variable locale est une référence à un objet Integer. La méthode définit la référence localVariable1 pour qu'elle pointe vers une nouvelle instance Integer. Le lien sera stocké dans sa propre copie de localVariable1 pour chaque thread. Les deux instances Integer seront stockées sur le tas et comme la méthode crée un nouvel objet Integer à chaque exécution, les deux threads exécutant cette méthode créeront des instances Integer distinctes. Ils correspondent à l'objet 1 et à l'objet 5 du schéma ci-dessus.



Notez également les deux variables membres de la classe MySharedObject de type long, qui est un type primitif. Étant donné que ces variables sont des variables membres, elles sont toujours stockées sur le tas avec l'objet. Seules les variables locales sont stockées sur la pile de threads.



La partie 2 est ici.



All Articles