Java programmation simultanée combat 02 comment résoudre le problème de visibilité et d'ordre

Résumé

Dans l'article précédent, j'ai mentionné que la mise en cache du processeur conduit à la visibilité, la commutation des threads conduit à l'atomicité et l'optimisation de la compilation entraîne des problèmes de commande . Ensuite, cet article résout d'abord les problèmes de visibilité et d'ordre, ce qui conduit au protagoniste d'aujourd'hui: le modèle de mémoire Java (sera fréquemment évalué lorsque l'interview est simultanée)

Qu'est-ce que le modèle de mémoire Java?

Maintenant que vous savez que la mise en cache du processeur entraîne une visibilité et que l'optimisation de la compilation entraîne des problèmes de commande , le moyen le plus simple consiste à désactiver directement la mise en cache du processeur et l'optimisation de la compilation. Mais ce faisant, nos performances sont sur le point d'exploser ~. Nous devons le désactiver si nécessaire.
Le modèle de mémoire Java a une spécification très compliquée, mais du point de vue du programmeur, il peut être compris comme: Le modèle de mémoire Java spécifie comment la JVM fournit une méthode pour désactiver la mise en cache et l'optimisation de la compilation à la demande .
Il comprend trois mots clés: volatile, synchronisé et final, et six règles Happens-Before.

mot-clé volatile

volatile signifie désactiver le cache CPU. Lorsque le cache CPU est désactivé, les variables de données sont directement lues et écrites directement depuis la mémoire. Par exemple volatile boolean v = false, si vous utilisez volatile pour déclarer une variable , vvous devez lire ou écrire à partir de la mémoire lorsque vous manipulez la variable , mais avant Java version 1.5, il peut y avoir des problèmes.
Dans le code suivant, supposons que le thread A exécute la writeméthode et le thread B exécute la readerméthode. En supposant que le thread B a jugé qu'il est this.v == trueentré dans la condition de jugement, quel sera x à ce moment?

public class VolatileExample {
    private int x = 0;
    private volatile boolean v = false;

    public void write() {
        this.x = 666;
        this.v = true;
    }

    public void reader() {
        if (this.v == true) {
            // 这里的x会是多少呢?
        }
    }
}

Avant la version 1.5, la valeur peut être 666 ou 0; car la variable xne désactive pas la mise en cache (volatile), mais après la version 1.5, la valeur doit être 666; en raison de la règle Happens-Before .

Quelles sont les règles qui se produisent avant

La règle Happens-Before est d'exprimer que le résultat de l'opération précédente est visible pour les suivantes . Si vous êtes exposé à cette règle pour la première fois, il peut y avoir une certaine confusion, mais la lire plusieurs fois approfondira votre compréhension.

1. Règles de procédure séquentielles

Cette règle signifie que dans un thread, selon l'ordre du programme, l'opération précédente Happens-Before est suivie de toute opération suivante (ce qui signifie que le résultat de l'opération précédente peut être vu pour toute opération ultérieure). Tout comme le code ci-dessus, dans l'ordre du programme: this.x = 666Happens-Before this.v = true.

2. Règles des variables volatiles

Cette règle fait référence à une opération d'écriture dans une variable volatile, opération de lecture Happens-Before de la variable. La signification est: en supposant que la variable est écrite par le thread A, alors la variable est visible par n'importe quel thread. Cela signifie que le cache CPU est désactivé.Si tel est le cas, il n'y a aucune différence par rapport à la version précédente 1.5! Ensuite, si nous regardons à nouveau la règle 3, ce sera différent.

3. Transmissibilité

Cette règle fait référence à: si A Happens-Before est B, et B Happens-Before est C. Ensuite, A Happens-Before et C. Ceci est la règle transitive. Jetons un coup d'œil au code tout à l'heure (je l'ai copié pour une visualisation facile)

public class VolatileExample {
    private int x = 0;
    private volatile boolean v = false;

    public void write() {
        this.x = 666;
        this.v = true;
    }

    public void reader() {
        if (this.v == true) {
            // 读取变量x
        }
    }
}

Dans le code ci-dessus, nous pouvons voir que this.x = 666Happens-Before this.v = true, this.v = trueHappens-Before 读取变量x, selon la règle transitive this.x = 666Happens-Befote 读取变量x, puis this.v = truelorsque la variable est lue , 读取变量xl'index doit alors 666
supposer que le thread A a exécuté la writeméthode, thread B exécute la readerméthode et à ce moment this.v == true, selon la règle de transitivité qui vient d'être mentionnée, la variable lue xdoit être 666. Il s'agit de l'amélioration de la sémantique volatile dans la version 1.5. Si c'était avant la version 1.5, parce que la variable xne désactive pas le cache (volatile), la variable xpeut être 0oh.

image.png

4. Les règles de verrouillage dans le processus

Cette règle se réfère à l'opération de déverrouillage d'un verrou Happens-Before et à l'opération de verrouillage ultérieure de la serrure. Le superviseur est une primitive de synchronisation générale. En Java, synchronisé est la réalisation du superviseur en Java.
Le verrou du processus est implémenté implicitement en Java. Par exemple, le code suivant sera automatiquement verrouillé avant d'entrer dans le bloc de code synchronisé et sera automatiquement déverrouillé après l'exécution du bloc de code. Le verrouillage et le déverrouillage ici sont tous mis en œuvre par le compilateur pour nous.

synchronized(this) { // 此处自动加锁
   // x是共享变量,初始值 = 0
   if (this.x < 12) {
      this.x = 12;
   }
} // 此处自动解锁

Combinée avec les règles de verrouillage du processus , xla valeur initiale est supposée être 0 et la valeur du thread A deviendra 12 après l'exécution du bloc de code par le thread A. Ensuite, lorsque le thread A se déverrouille, le thread B peut obtenir le verrou et entrer dans le bloc de code. un fil de résultats d'exécution x = 12. Ceci est la règle de verrouillage dans le processus

5. La règle start () du thread

Cette règle concerne le démarrage du thread. Cette règle fait référence à l'opération avant que le thread principal A démarre le thread enfant B après que le thread principal A démarre le thread enfant B.
Expliquez avec HappensBefore: le thread A appelle la méthode de démarrage du thread B Happens-Before toute opération dans le thread B. Le code de référence est le suivant:

    int x = 0;
    public void start() {
        Thread thread = new Thread(() -> {
            System.out.println(this.x);
        });

        this.x = 666;
        // 主线程启动子线程
        thread.start();
    }

À ce stade, la xvaleur variable imprimée dans le thread enfant est 666, vous pouvez également l'essayer.

6. Règles de jointure de thread ()

Cette règle concerne le thread en attente. La règle fait référence au thread principal A en attente de la fin du sous-thread B (le thread principal A est implémenté en appelant le sous-thread B join()). Une fois le sous-thread B terminé, le thread principal peut voir L'opération, voir ici fait référence au fonctionnement des variables partagées , expliqué par Happens-Before: Si la join()méthode du sous-thread B est appelée dans le thread A et revient avec succès, alors toute opération du sous-thread B Happens-Before est appelée à partir du thread principal join()Opération ultérieure de la méthode du sous-thread B. Le code est plus facile à comprendre. L'exemple de code est le suivant:

    int x = 0;
    public void start() {
        Thread thread = new Thread(() -> {
            this.x = 666;
        });
        // 主线程启动子线程
        thread.start();
        // 主线程调用子线程的join方法进行等待
        thread.join();
        // 此时的共享变量 x == 666
    }

Finale négligée

Avant la version 1.5, sauf que la valeur ne pouvait pas être modifiée, les finalchamps étaient en fait les mêmes que les champs ordinaires.
Dans le modèle de mémoire Java après 1.5, il finalexiste des contraintes sur le réarrangement des variables de type. Tant que notre constructeur correct n'est pas échappé , finalla dernière valeur du champ initialisé dans le constructeur doit être vue par les autres threads. Le code est le suivant:

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }

Lorsque le thread exécute la reader()méthode et l' f != nullheure, la finalmodification de champ à ce moment f.xdoit être 3, mais elle yne peut pas être garantie 4car elle ne l'est pas final. Si c'est avant la version 1.5, alors ce f.xn'est pas garanti 3.
Alors qu'est-ce que l' évasion ? Modifions le constructeur:

  public FinalFieldExample() {
    x = 3;
    y = 4;
    // 此处为逸出
    f = this;
  }

Ici , il ne peut pas être garantie f.x == 3, même si xla variable est finalmodifiée, et pourquoi? Étant donné que le réarrangement des instructions peut se produire dans le constructeur, l'exécution devient la suivante:

     // 此处为逸出
    f = this;
    x = 3;
    y = 4;

Puis à ce moment f.x == 0. Il n'y a donc pas d' échappatoire dans le constructeur , alors le champ modifié final ne pose aucun problème. Pour des cas détaillés, veuillez vous référer à ce document

Résumé

Dans cet article, je finaln'ai pas compris le réarrangement des contraintes dans la dernière partie de l'article . Ce n'est qu'après avoir continué à rechercher des informations sur Internet et lu les informations fournies dans l'article que je l'ai lentement compris. Peut-être que votre esprit n'est pas flexible.
Le contenu principal de cet article est la règle Happens-Before, et il est correct de comprendre ces règles.

Article de référence: Geek Time: Java Concurrent Programming Practice 02

URL du blog personnel: https://colablog.cn/

Si mon article vous aide, vous pouvez suivre mon compte public WeChat et partager l'article avec vous dès que possible
Compte public WeChat

Publié 19 articles originaux · louange gagné 2 · Vues 2537

Je suppose que tu aimes

Origine blog.csdn.net/Lin_JunSheng/article/details/105572089
conseillé
Classement