Cohérence de la mémoire-arrive - avant la relation en Java [dupliquer]

cette question a déjà une réponse ici:

en lisant Java docs sur les erreurs de cohérence de la mémoire. Je trouve des points liés à deux actions qui crée se produisent-avant la relation:

  • Lorsqu'une déclaration invoque Thread.start() , chaque déclaration qui a -passe-avant relation avec cette déclaration a également un se produit-avant la relation avec chaque déclaration exécutée par le nouveau fil. Les effets du code qui a conduit à la création de l' nouveau thread sont visibles pour le nouveau thread.

  • Lorsqu'un fil se termine et provoque un Thread.join() dans un autre fil pour retourner, puis toutes les déclarations exécutées par la résiliation

    fil ont un passe-avant relation avec tous les états

    après le succès de la rejoindre. Les effets du code dans le thread sont maintenant visibles au fil qui a exécuté la jointure.

Je ne suis pas capable de comprendre leur signification. Il serait génial si quelqu'un l'expliquer avec un exemple simple.

33
demandé sur Vladimir Vagaytsev 2013-04-27 10:02:40

4 réponses

les CPU modernes n'écrivent pas toujours les données en mémoire dans l'ordre où elles ont été mises à jour, par exemple si vous exécutez le pseudo-code (en supposant que les variables sont toujours stockées en mémoire ici pour la simplicité);

a = 1
b = a + 1

...le CPU peut très bien écrire b en mémoire avant d'écrire a en mémoire. Ce n'est pas vraiment un problème tant que vous exécutez les choses dans un seul thread, depuis le thread qui exécute le code ci-dessus ne verrez jamais l'ancienne valeur de variable une fois les affectations effectuées.

Multi-threading est une autre question, vous pensez que le code suivant devrait laisser un autre thread ramasser la valeur de votre lourde de calcul;

a = heavy_computation()
b = DONE

...l'autre thread en train de faire...

repeat while b != DONE
    nothing

result = a

le problème cependant est que le drapeau fait peut être réglé en mémoire avant que le résultat soit stocké en mémoire, donc l'autre fil peut prendre la valeur de mémoire adresse avant le résultat du calcul est écrit à la mémoire.

le même problème serait - si Thread.start et Thread.join n'ont pas eu un" se produit avant "garantie - vous donner des problèmes avec le code comme;

a = 1
Thread.start newthread
...

newthread:
    do_computation(a)

...depuis a peut ne pas avoir une valeur stockée en mémoire lorsque le thread commence.

puisque vous voulez presque toujours le nouveau thread pour être en mesure d'utiliser des données vous initialisé avant de le démarrer, Thread.start a une" happens before "garantie, c'est-à-dire les données qui ont été mises à jour avant d'appeler Thread.start sont garanties d'être disponibles pour le nouveau thread . Il en va de même pour Thread.join les données écrites par le nouveau thread sont garanties pour être visibles au thread qui le rejoint après la terminaison .

il rend juste filetage beaucoup plus facile.

28
répondu Joachim Isaksson 2013-04-27 06:29:27

considérez ceci:

static int x = 0;

public static void main(String[] args) {
    x = 1;
    Thread t = new Thread() {
        public void run() {
            int y = x;
        };
    };
    t.start();
}

le thread principal a changé le champ x . Le modèle de mémoire Java ne garantit pas que ce changement sera visible par les autres threads s'ils ne sont pas synchronisés avec le thread principal. Mais le fil t verra ce changement parce que le fil principal appelé t.start() et JLS garantit que l'appel t.start() rend le changement à x visible dans t.run() ainsi y est garanti pour être assigné 1 .

Les mêmes préoccupations Thread.join();

18
répondu Evgeniy Dorofeev 2016-07-17 17:54:26

des problèmes de visibilité de Thread peuvent survenir dans un code qui n'est pas correctement synchronisé selon le modèle de mémoire java. En raison des optimisations du compilateur et du matériel, les Écritures d'un thread ne sont pas toujours visibles par les lectures d'un autre thread. Le modèle de mémoire Java est un modèle formel qui rend les règles de "correctement synchronisées" claires, de sorte que les programmeurs peuvent éviter des problèmes de visibilité de thread.

arrive-avant est un rapport défini dans ce modèle, et il se réfère à des exécutions spécifiques. Une écriture W qui s'avère être se produit-avant un read R est garanti d'être visible par cette lecture, en supposant qu'il n'y a pas d'autre écriture interférente (c.-à-d. un sans se produit-avant la relation avec la lecture, ou un qui se produit entre eux selon cette relation).

le type le plus simple de se produit-avant que la relation se produit entre les actions dans le même fil. Une écriture W à V dans le fil P se produit-avant une lecture R de V dans le même fil, en supposant que W vient avant R selon l'ordre du programme.

le texte auquel vous faites référence indique ce thread.start() et du fil.join () garantit aussi happens-before relationship. Toute action qui se produit-avant fil.start () se produit aussi avant toute action dans ce thread. De même, les actions dans le thread se produisent avant toute action qui apparaît après le thread.rejoindre.)(

Quel est le la signification pratique de qui? Si par exemple vous démarrez un thread et attendez qu'il se termine d'une manière non sûre (par exemple un sommeil pendant une longue période, ou tester un drapeau non synchronisé), alors quand vous allez essayer de lire les modifications de données effectuées par le thread, vous pouvez les voir partiellement, ce qui a le risque d'incohérences de données. La méthode join () agit comme une barrière qui garantit que n'importe quelle donnée publiée par le thread est visible complètement et uniformément par l'autre thread.

7
répondu Eyal Schneider 2013-04-27 07:06:59

selon le document oracle, ils définissent que la relation happens-before est simplement une garantie que mémoire écrit par une déclaration spécifique sont visible à une autre déclaration spécifique.

package happen.before;

public class HappenBeforeRelationship {


    private static int counter = 0;

    private static void threadPrintMessage(String msg){
        System.out.printf("[Thread %s] %s\n", Thread.currentThread().getName(), msg);
    }

    public static void main(String[] args) {

        threadPrintMessage("Increase counter: " + ++counter);
        Thread t = new Thread(new CounterRunnable());
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            threadPrintMessage("Counter is interrupted");
        }
        threadPrintMessage("Finish count: " + counter);
    }

    private static class CounterRunnable implements Runnable {

        @Override
        public void run() {
            threadPrintMessage("start count: " + counter);
            counter++;
            threadPrintMessage("stop count: " + counter);
        }

    }
}

sortie sera:

[Thread main] Increase counter: 1
[Thread Thread-0] start count: 1
[Thread Thread-0] stop count: 2
[Thread main] Finish count: 2

Avoir un oeil de sortie, la ligne [Thread Thread-0] début count: 1 montre que toutes les contre-modifications avant l'invocation de Fil.start () sont visibles dans le corps du Thread.

et la ligne [Thread main] Finish count: 2 indique que tous les changements dans le corps du Thread sont visibles pour le thread principal qui appelle Thread.rejoindre.)(

J'espère qu'il pourra vous aider clairement.

1
répondu Ken Block 2014-09-05 08:07:33