2
votes

Variables volatiles Java et getters synchronisés

Je fais des expériences avec la concurrence Java avec le livre Java "Concurrency in practice".

J'ai la question suivante: La méthode getter synchronisée est-elle égale à une variable privée volatile? Un peu de code:

private static volatile boolean ready;
...
while (!ready) {
    ...
}

Cette variante me donne le message "from class:". Comme

public class ConsoleApp {

private static boolean ready;
private static int number;



public synchronized static boolean isReady() {
    return ready;
}



private static class ReaderThread extends Thread {
    public void run() {
        while (!isReady()) {
            //Thread.yield();  // jvm updates variables on sleeping thread sometimes 
        }
        System.out.println("from class: " + number);
    }
}



public static void main ( String [] arguments ) throws InterruptedException
{
    System.out.println("start");
    Thread.sleep(3000);

    ready = false;
    number = 23;

    System.out.println("inited variable");
    Thread.sleep(3000);

    new ReaderThread().start();

    System.out.println("thread started");
    Thread.sleep(3000);

    number = 42;
    ready = true;

    System.out.println("variables changed");
    Thread.sleep(3000);


    System.out.println("ended;: " + number);
    Thread.sleep(8000);
    System.out.println("end" + number);

}
}

Quelle est la différence en termes d'exécution de code? Je sais que "synchonized" signifie "un accès au thread", et volatile signifie "obtenir la valeur du thread principal sans mise en cache"

Mais pourquoi le comportement de deux variantes est-il le même ??

p>


4 commentaires

"La méthode de lecture synchronisée est-elle égale à la variable privée volatile" Bien avant même d'entrer dans les aspects les plus complexes, évidemment non. Un getter expose une valeur et une variable privée ne le fait pas.


La synchronisation est un moyen d'empêcher deux threads d'appeler un morceau de code en même temps. Volatile est une indication qu'une variable peut changer sa valeur dans une autre thead pendant qu'un thread la regarde. Il y a des chevauchements, mais ils ne sont pas les mêmes.


La méthode non synchronisée fonctionne comme une variante non volatile, sans le message "from class:" du thread. Variable mise en cache de méthode non synchronisée également.


Le comportement peut être différent dans certains cas que vous n'avez pas mentionnés.


3 Réponses :


1
votes

Seule la libération et l'acquisition ultérieure du verrou établissent une relation arrive avant . Vous devez donc utiliser la méthode synchronized pour obtenir et définir la valeur d'une variable ready .

Mais dans votre exemple, préparez ready volatile suffit pour définir une nouvelle valeur pour la variable et obtenir la valeur de la variable pour établir également happens-before^.

L'essentiel est que vous soyez cohérent. Si vous utilisez des méthodes synchronisées pour la variable, tous les accès à cette variable doivent être effectués via ces méthodes synchronisées uniquement.

L'exemple de votre question fonctionnera toujours de manière prévisible avec volatile mais avec seulement le setter marqué comme synchronisé il n'y a pas de happening-before entre

while (!isReady()) 

dans le fil principal et

ready = true;

dans le deuxième fil. Le modèle de mémoire JVM ne garantit donc rien ici. Cela pourrait fonctionner comme vous vous en doutez.


1 commentaires

Les deux premiers paragraphes sont contradictoires.



1
votes

Le problème avec la simultanéité est que vous devez réduire vos attentes en matière de certitude et de prévisibilité. L'ordinateur faisait cela lorsque vous faisiez des choses avec un seul thread. Maintenant que vous avez affaire à la concurrence, ce ne sera pas le cas. Vous ne pouvez pas demander «pourquoi ceci ou cela ressemble-t-il» ou «pourquoi ceci ou cela est-il différent»? Tout ce que vous pouvez demander, c'est si quelque chose est garanti ou non.

Les deux versions de code donnent les mêmes résultats lorsque vous l'essayez sur votre ordinateur en utilisant vos versions de vos outils, car rien n'impose qu'ils se comportent différemment. Rien n'exige non plus qu'ils fassent la même chose. Il se passera quelque chose avec ces codes, et vous pouvez en prévoir une partie et pas le reste.

Quoi qu'il en soit, la différence avec la méthode synchronisée ou la variable volatile, c'est que tout accès, lecture ou écriture, à une variable volatile est garanti comme s'il n'y avait qu'une seule mémoire principale et que les caches de mémoire locale des threads n'existaient pas. Les méthodes synchronisées ne le font que lorsque vous entrez la méthode synchronisée. Ici, vous ne synchronisez que lors de la lecture de la variable, et vous ne faites aucune manipulation appropriée en écriture.

Cela signifie que votre exemple avec synchronize a eu beaucoup de chances de ne pas donner les résultats escomptés. Non pas qu'il était garanti d'échouer. Peut-être que ce sera le cas, peut-être que ce ne sera pas le cas. Tout ce que nous pouvons dire, c'est ce qui est garanti par une programmation appropriée. Nous ne pouvons pas dire ce qui arrivera avec ce que votre programmation a laissé inconnu.


0 commentaires

3
votes

La méthode getter synchronisée est-elle égale à une variable privée volatile?

Non.

L'équivalence est avec un setter synchronisé ET un getter synchronisé versus une variable volatile private .

Si vous utilisez un getter synchronisé et un setter simple (non synchronisé), il n'y aura pas de relation entre l'écriture d'un thread et la lecture (ultérieure) de la variable par un autre thread.

L'autre point est que le getter et le setter doivent se synchroniser sur la même chose.

Enfin, un getter ou un setter peut faire quelque chose de plus compliqué que de lire ou d'écrire une variable. Dans ce cas, la construction synchronized garantit que toutes les actions sont effectuées de manière atomique par rapport aux autres threads synchronisés sur le même verrou. En revanche, une variable volatile ne peut pas vous donner une garantie d'atomicité.


Mais pourquoi le comportement de deux variantes est-il le même?

Vous avez de la chance!

Le modèle de mémoire Java indique lorsqu'une opération de lecture est garantie de voir le résultat d'une opération de lecture précédente. Si vous le faites correctement, votre application se comportera correctement.

L'inverse n'est pas le cas. Si vous le faites mal, vous pouvez obtenir le comportement correct de toute façon (!) Mais ce n'est pas garanti:

  • Vous pouvez toujours l'obtenir sur une plate-forme particulière; par exemple. matériel avec un nombre donné de cœurs et une architecture mémoire donnée.

  • Vous pouvez toujours l'obtenir avec une version donnée de Java.

  • Ou vous pouvez avoir le bon comportement ... sauf dans des circonstances rares ou inhabituelles.

Mais il n'y a aucune garantie de comportement si vous ne faites pas ce que le modèle de mémoire Java requiert.


Quelques conséquences:

  • Les tests ne prouveront pas que votre code concurrent correct est correct. Au mieux, cela peut montrer qu'il est incorrect.

  • Tester pour prouver qu'un code concurrent incorrect est incorrect est également difficile. Vous constaterez peut-être que votre code incorrect semble fonctionner correctement.


0 commentaires