27 votes

Comportement de la barrière mémoire en Java

Après la lecture de plusieurs blogs/articles etc, maintenant, je suis vraiment confus sur le comportement de load/store avant/après la barrière de mémoire.

Suivantes sont 2 des citations de Doug Lea dans l'un de ses clarification de l'article sur JMM, qui sont à la fois très compliqué:

  1. Tout ce qui était visible à enfiler quand il écrit à la volatilité du champ f devient visible pour le fil B lorsqu'il lit.f.
  2. Notez qu'il est important pour les deux threads d'accéder à la même volatilité variable afin de configurer correctement le passe-avant la relation. Ce n'est pas le cas, que tout ce qui est visible à enfiler lors de l'écriture du champ volatile f devient visible pour le fil B, après lecture champ volatile g.

Mais alors quand j'ai regardé dans un autre blog sur la mémoire de la barrière, j'ai eu ces:

  1. Un magasin de la barrière, "sfence" instruction sur x86, les forces de stocker toutes les instructions avant la barrière pour arriver avant la barrière et ont la stocker des tampons vidé le cache du PROCESSEUR sur lequel il est délivré.
  2. Une charge de la barrière, "lfence" instruction sur x86, les forces toutes les instructions de chargement après la barrière à se produire après la barrière, puis d'attendre le chargement de la mémoire tampon à égoutter pendant que le CPU.

Pour moi, Doug Lea clarification est plus stricte que les autres: fondamentalement, cela signifie que si la charge de la barrière et de stocker un obstacle sur les différents moniteurs, la cohérence des données ne sera pas garantie. Mais la plus tard l'un des moyens, même si les obstacles sont sur différents moniteurs, la cohérence des données sera garantie. Je ne suis pas sûr si je la compréhension de ces 2 correctement et aussi je ne suis pas sûr de qui est correct.

Considérant les codes suivants:

  public class MemoryBarrier {
    volatile int i = 1, j = 2;
    int x;

    public void write() {
      x = 14; //W01
      i = 3;  //W02
    }

    public void read1() {
      if (i == 3) {  //R11
        if (x == 14) //R12
          System.out.println("Foo");
        else
          System.out.println("Bar");
      }
    }

    public void read2() {
      if (j == 2) {  //R21
        if (x == 14) //R22
          System.out.println("Foo");
        else
          System.out.println("Bar");
      }
    }
  }

Disons que nous avons 1 écrire fil TW1 le premier appel de la MemoryBarrier de la méthode write (), puis nous avons 2 threads de lecture de TR1 et TR2 appel MemoryBarrier de read1() et read2() la méthode.Estime que ce programme est exécuté sur un PROCESSEUR de ne pas préserver l'ordre (x86 FAIRE préserver l'ordre pour de tels cas, qui n'est pas le cas), selon le modèle de mémoire, il y aura un StoreStore barrière (disons SB1) entre W01/W02, ainsi que 2 LoadLoad barrière entre R11/R12 et R21/R22 (disons RB1 et RB2).

  1. Depuis SB1 et RB1 sont sur le même moniteur que j', fil TR1 qui appelle read1 devriez toujours voir 14 sur x, "Foo" est toujours imprimé.
  2. SB1 et RB2 sont sur différents moniteurs, si Doug Lea est correct, le fil TR2 ne sera pas assuré de voir 14 sur x, ce qui signifie "Bar" peut être imprimé à l'occasion. Mais si la mémoire de la barrière fonctionne comme Martin Thompson décrit dans le blog, la Boutique de la barrière de pousser toutes les données de la mémoire principale et de la Charge de la barrière va tirer toutes les données de la mémoire principale à cache/tampon, puis TR2 sera également assurée pour voir 14 sur x.

Je ne sais pas laquelle est la bonne, ou les deux d'entre eux sont, mais ce Martin Thompson décrit, c'est juste pour l'architecture x86. JMM ne garantit pas le changement de x est visible à TR2 mais x86 mise en œuvre.

Merci~

10voto

nosid Points 20267

Doug Lea est à droite. Vous pouvez trouver à la partie pertinente de l'article §17.4.4 de la Java Language Specification:

§17.4.4 L'Ordre De Synchronisation

[..] Une écriture dans un contexte de volatilité variable v (§8.3.1.4) synchronise avec toutes les lectures subséquentes de v par n'importe quel thread (où "après" est défini en fonction de l'ordre de synchronisation). [..]

Le modèle de mémoire du béton machine n'a pas d'importance, parce que la sémantique du Langage de Programmation Java sont définis en termes d'une machine abstraite - indépendants de le béton machine. Il est de la responsabilité de l'environnement d'exécution Java pour exécuter le code d'une façon telle, qu'il est en conformité avec les garanties données par le Langage Java Spécification.


Concernant le libellé de la question:

  • Si il n'y a plus de synchronisation, la méthode read2 pouvez imprimer "Bar"car read2 peut être exécutée avant l' write.
  • Si il ya un supplément de synchronisation avec un CountDownLatch à assurez-vous que read2 est exécutée après l' write, alors la méthode de read2 ne sera jamais imprimer "Bar", en raison de la synchronisation avec CountDownLatch supprime les données de course sur x.

Indépendant des variables volatiles:

Est-il sensé que l'écriture d'une variable volatile n'est pas en synchronisation avec la lecture de tout autre volatile variable?

Oui, c'est logique. Si deux threads ont besoin d'interagir les uns avec les autres, ils ont généralement d'utiliser le même volatile variable afin d'échanger des informations. D'autre part, si un thread utilise une variable volatile sans avoir besoin d'interagir avec tous les autres threads, nous ne voulons pas payer le coût d'une barrière de mémoire.

Il est effectivement important dans la pratique. Nous allons faire un exemple. La classe suivante utilise une volatilité variable de membre:

class Int {
    public volatile int value;
    public Int(int value) { this.value = value; }
}

Imaginez cette classe est utilisée que localement à l'intérieur d'une méthode. Le compilateur JIT peut facilement détecter, que l'objet est utilisé uniquement à l'intérieur de cette méthode (Échapper à l'analyse).

public int deepThought() {
    return new Int(42).value;
}

Avec la règle ci-dessus, le compilateur JIT pouvez supprimer tous les effets de l' volatile les lectures et les écritures, parce que l' volatile variable ne peut pas avoir des accès à partir de n'importe quel autre thread.

Cette optimisation existe réellement dans le compilateur Java JIT:

1voto

Alexey Malev Points 3378

Que j'ai compris la question est en fait sur les volatiles de lecture/écriture et sa se passe-avant de garanties. En parlant de cette partie, je n'ai qu'une chose à ajouter à nosid de réponse:

Volatile écrit ne peut pas être déplacé à l'avant de la normale écrit, la volatilité lit ne peut pas être déplacé après les lectures normales. C'est pourquoi, read1() et read2() résultat sera comme nosid écrit.

S'exprimant sur les obstacles - l'defininition sonne bien pour moi, mais la seule chose probablement confondu vous, c'est que ce sont des choses/outils/moyen de/mécanisme de l'appeler ce que vous voulez implémenter le comportement décrit dans JMM en hotspot. Lors de l'utilisation de Java, vous devez compter sur JMM garanties, pas de détails de mise en œuvre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X