68 votes

Pourquoi volatils dans java 5+ ne pas synchroniser les copies mises en cache des variables avec la mémoire principale?

Selon:

http://www.ibm.com/developerworks/library/j-jtp03304/

En vertu de la nouvelle mémoire de modèle, lorsque le thread d'Un écrit à un volatile variable V, et le fil B lit à partir de V, toutes les valeurs des variables qui ont été visible au moment de la V a été écrit sont garantis dès maintenant pour être visible à B

Et beaucoup d'endroits sur l'internet de l'état que le code suivant ne doit jamais print "erreur":

public class Test {
    volatile static private int a;
    static private int b;

    public static void main(String [] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            new Thread() {

                @Override
                public void run() {
                    int tt = b; // makes the jvm cache the value of b

                    while (a==0) {

                    }

                    if (b == 0) {
                        System.out.println("error");
                    }
                }

            }.start();
        }

        b = 1;
        a = 1;
    }
}

b devrait être de 1 pour tous les threads lorsqu' a est de 1.

Cependant, parfois, j'ai "erreur" imprimé. Comment est-ce possible?

34voto

John Vint Points 19804

Mise à jour:

Pour toute personne intéressée, ce bug a été résolu et fixe pour Java 7u6 construire b14. Vous pouvez voir le rapport de bug/bugs ici

Réponse Originale À Cette Question

Lorsque l'on pense en termes de mémoire de la visibilité/commande, vous auriez besoin de penser à propos de son-passe-avant la relation. La pré condition pour b != 0 est a == 1. Si a != 1 alors b peut être 0 ou 1.

Une fois qu'un thread voit a == 1 alors que le thread est garanti pour voir b == 1.

Post Java 5, dans l'OP exemple, une fois que l' while(a == 0) éclate b est garantie 1

Edit:

J'ai couru à la simulation nombre de fois et de ne pas voir votre sortie.

Quel système d'exploitation, la version Java & CPU êtes-vous tester?

Je suis sur Windows 7, Java 1.6_24 (essayer avec _31)

Edit 2:

Bravo à l'OP et Walter Laan - Pour moi, il ne s'est passé lorsque je suis passé de 64 bits de Java 32 bits de Java, sur (mais ne peut pas être exclue) une version 64 bits de windows 7.

Edit 3:

La cession tt, ou plutôt la staticget d' b semble avoir un impact significatif (pour prouver cette suppression de l' int tt = b; et il devrait toujours fonctionner.

Il semble que la charge de b en tt va stocker le champ localement qui sera ensuite utilisé dans le si coniditonal (la référence à cette valeur non tt). Donc, si b == 0 est vrai, il est probable que le magasin local d' tt a 0 (à ce point de sa course à affecter 1 à local tt). Cela semble être vrai pour les 32 Bits de Java 1.6 & 7 avec l'ensemble de client.

J'ai comparé les deux sortie de l'assemblée et de la différence immédiate était ici. (Gardez à l'esprit ce sont des fragments de code).

Cet imprimé "erreur"

 0x021dd753: test   %eax,0x180100      ;   {poll}
  0x021dd759: cmp    $0x0,%ecx
  0x021dd75c: je     0x021dd748         ;*ifeq
                                        ; - Test$1::run@7 (line 13)
  0x021dd75e: cmp    $0x0,%edx
  0x021dd761: jne    0x021dd788         ;*ifne
                                        ; - Test$1::run@13 (line 17)
  0x021dd767: nop    
  0x021dd768: jmp    0x021dd7b8         ;   {no_reloc}
  0x021dd76d: xchg   %ax,%ax
  0x021dd770: jmp    0x021dd7d2         ; implicit exception: dispatches to 0x021dd7c2
  0x021dd775: nop                       ;*getstatic out
                                        ; - Test$1::run@16 (line 18)
  0x021dd776: cmp    (%ecx),%eax        ; implicit exception: dispatches to 0x021dd7dc
  0x021dd778: mov    $0x39239500,%edx   ;*invokevirtual println

Et

Cela n'a pas l'impression "d'erreur"

0x0226d763: test   %eax,0x180100      ;   {poll}
  0x0226d769: cmp    $0x0,%edx
  0x0226d76c: je     0x0226d758         ;*ifeq
                                        ; - Test$1::run@7 (line 13)
  0x0226d76e: mov    $0x341b77f8,%edx   ;   {oop('Test')}
  0x0226d773: mov    0x154(%edx),%edx   ;*getstatic b
                                        ; - Test::access$0@0 (line 3)
                                        ; - Test$1::run@10 (line 17)
  0x0226d779: cmp    $0x0,%edx
  0x0226d77c: jne    0x0226d7a8         ;*ifne
                                        ; - Test$1::run@13 (line 17)
  0x0226d782: nopw   0x0(%eax,%eax,1)
  0x0226d788: jmp    0x0226d7ed         ;   {no_reloc}
  0x0226d78d: xchg   %ax,%ax
  0x0226d790: jmp    0x0226d807         ; implicit exception: dispatches to 0x0226d7f7
  0x0226d795: nop                       ;*getstatic out
                                        ; - Test$1::run@16 (line 18)
  0x0226d796: cmp    (%ecx),%eax        ; implicit exception: dispatches to 0x0226d811
  0x0226d798: mov    $0x39239500,%edx   ;*invokevirtual println

Dans cet exemple, la première entrée est à partir d'une série qui a imprimé "erreur", tandis que le deuxième a été l'un qui na pas.

Il semble que l'exécution de travail chargé et attribué b correctement avant de le tester égal à 0.

  0x0226d76e: mov    $0x341b77f8,%edx   ;   {oop('Test')}
  0x0226d773: mov    0x154(%edx),%edx   ;*getstatic b
                                        ; - Test::access$0@0 (line 3)
                                        ; - Test$1::run@10 (line 17)
  0x0226d779: cmp    $0x0,%edx
  0x0226d77c: jne    0x0226d7a8         ;*ifne
                                        ; - Test$1::run@13 (line 17)

Lors de l'exécution, qui a imprimé "erreur" chargé la version en cache d' %edx

  0x021dd75e: cmp    $0x0,%edx
  0x021dd761: jne    0x021dd788         ;*ifne
                                        ; - Test$1::run@13 (line 17)

Pour ceux qui ont plus d'expérience avec l'assembleur veuillez peser :)

Edit 4

Devrait être mon dernier montage, comme la simultanéité dev obtenir une main sur elle, j'ai fait le test avec et sans le int tt = b; affectation un peu plus. J'ai constaté que lorsque j'augmente le max de 100 à 1000 il semble y avoir un 100% de taux d'erreur lors de l' int tt = b est inclus et 0% de chances lorsqu'il n'est pas exclu.

12voto

assylias Points 102015

Basé sur l'extrait de JCiP ci-dessous, j'aurais pensé que votre exemple ne doit jamais print "erreur":

La visibilité des effets de la volatilité des variables de prolonger au-delà de la valeur de la volatilité de la variable elle-même. Lorsqu'un thread d'Un écrit à un volatile variable et par la suite thread B lit la même variable, les valeurs de toutes les variables qui étaient visibles à Un avant d'écrire à la volatilité des variables deviennent visibles à B après la lecture de la volatilité de la variable.

2voto

sjlee Points 3457

Vous voudrez peut-être consulter un fil de discussion sur la simultanéité de l'intérêt liste de diffusion sur cette question: http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html

Il me semble que le problème est plus facile de reproduire avec le client de la JVM (client).

-2voto

Ofek Ron Points 1399

à Mon avis,Le Problème acurred en raison du Manque de Synchronisation :

AVIS : si b=1 heppens avant de a=1, et a est volatile, tandis que b ne l'est pas, alors b=1 en fait des mises à jour pour tous les threads qu'après un=1 est terminé (selon la quate de la logique).

ce heppend dans votre code, c'est que b=1 a d'abord été mis à jour pour le processus principal seulement, alors que lorsque la volatilité de l'affectation terminée, tous les fils b est mis à jour. Je pense que peut-être les affectations de la volatilité ne fonctionnent pas comme des opérations atomiques (point de mesure, et en quelque sorte de mise à jour de repos de refernces à agir comme des composés volatils) alors ce serait mon deviner pourquoi un thread de lecture b=0 au lieu de b=1.

Prendre en compte ce changement pour le code, ce qui montre que ma demande:

public class Test {
    volatile static private int a;
    static private int b;
    private static Object lock = new Object();


    public static void main(String [] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            new Thread() {

                @Override
                public void run() {
                    int tt = b; // makes the jvm cache the value of b

                    while (true) {
                        synchronized (lock ) {
                            if (a!=0) break;
                         }
                    }

                    if (b == 0) {
                        System.out.println("error");
                    }
                }

            }.start();
        }
        b = 1;
        synchronized (lock ) {
        a = 1;
        }  
    }
}

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