J'ai lu " Quand utiliser 'volatile' en Java? " mais je suis toujours confus. Comment savoir quand je dois marquer une variable comme volatile? Et si je me trompe, soit en omettant un volatile sur quelque chose qui en a besoin, soit en mettant du volatile sur quelque chose qui n'en a pas? Quelles sont les règles de base pour déterminer quelles variables doivent être volatiles dans le code multithread?
Vous l'utilisez essentiellement lorsque vous souhaitez autoriser l'accès à une variable membre par plusieurs threads, mais que vous n'avez pas besoin d'atomicité composée (vous ne savez pas si c'est la bonne terminologie).
class BadExample {
private volatile int counter;
public void hit(){
/* This operation is in fact two operations:
* 1) int tmp = this.counter;
* 2) this.counter = tmp + 1;
* and is thus broken (counter becomes fewer
* than the accurate amount).
*/
counter++;
}
}
ce qui précède est un mauvais exemple, car vous avez besoin d'atomicité composée.
class BadExampleFixed {
private int counter;
public synchronized void hit(){
/*
* Only one thread performs action (1), (2) at a time
* "atomically", in the sense that other threads can not
* observe the intermediate state between (1) and (2).
* Therefore, the counter will be accurate.
*/
counter++;
}
}
Passons maintenant à un exemple valide:
class GoodExample {
private static volatile int temperature;
//Called by some other thread than main
public static void todaysTemperature(int temp){
// This operation is a single operation, so you
// do not need compound atomicity
temperature = temp;
}
public static void main(String[] args) throws Exception{
while(true){
Thread.sleep(2000);
System.out.println("Today's temperature is "+temperature);
}
}
}
Maintenant, pourquoi ne pouvez-vous pas simplement utiliser private static int temperature
? En fait, vous pouvez (dans le sens où votre programme ne va pas exploser ou quelque chose comme ça), mais le changement de temperature
par l'autre thread peut ou peut ne pas être "visible" pour le thread principal.
Fondamentalement, cela signifie qu'il est même possible que votre application. continue d'écrire Today's temperature is 0
pour toujours si vous n'utilisez pas volatile
(en pratique, la valeur tend à devenir éventuellement visible. Cependant, vous ne devriez pas risquer de ne pas utiliser volatile lorsque nécessaire, car il peut conduire à des bugs désagréables (causés par des objets de construction incomplète, etc.).
Si vous placez le mot clé volatile
sur quelque chose qui n'a pas besoin de volatile
, cela n'affectera pas l'exactitude de votre code (c'est-à-dire que le comportement ne changera pas). En termes de performances, cela dépendra de l'implémentation de la JVM. En théorie, vous pourriez obtenir une minuscule dégradation des performances parce que le compilateur ne peut pas réorganiser les optimisations, doit invalider le cache du processeur, etc., mais là encore, le compilateur peut prouver que votre champ ne peut jamais être accessible par plusieurs threads et supprimer l'effet de volatile
mot-clé complètement et le compiler en instructions identiques.
MODIFIER:
Réponse à ce commentaire:
D'accord, mais pourquoi ne pouvons-nous pas synchroniser todaysTemperature et créer un getter synchronisé pour la température?
Vous pouvez et il se comportera correctement. Tout ce que vous pouvez avec volatile
peut être fait avec synchronized
, mais pas l'inverse. Vous pouvez préférer volatile
pour deux raisons:
volatile
est moins sujette aux bogues de concurrence, comme le blocage tout en maintenant le verrou, les interblocages, etc.volatile
peut avoir un débit significativement plus élevé et une meilleure latence. Cependant, dans la plupart des applications, la différence est trop petite pour être importante.Volatile est plus utile dans les algorithmes sans verrouillage. Vous marquez la variable contenant des données partagées comme volatile lorsque vous n'utilisez pas le verrouillage pour accéder à cette variable et que vous souhaitez que les modifications apportées par un thread soient visibles dans un autre, ou que vous souhaitiez créer une relation "se produit après" pour vous assurer que le calcul est pas réordonné, encore une fois, pour s'assurer que les changements deviennent visibles au moment opportun.
Le JMM Cookbook décrit quelles opérations peuvent être réorganisées et lesquelles ne le peuvent pas.
volatile
mot-clé garantit que la valeur de la variable volatile sera toujours lue dans la mémoire principale et non dans le cache local de Thread.
De Java simultanéité tutoriel :
L'utilisation de variables volatiles réduit le risque d'erreurs de cohérence de la mémoire, car toute écriture dans une variable volatile établit une relation passe-avant avec les lectures suivantes de cette même variable
Cela signifie que les modifications apportées à une variable volatile sont toujours visibles pour les autres threads. Cela signifie également que lorsqu'un thread lit une variable volatile, il voit non seulement la dernière modification apportée à la variable volatile, mais également les effets secondaires du code qui a provoqué la modification.
Concernant votre requête:
Comment savoir quand je dois marquer une variable comme volatile? Quelles sont les règles de base pour déterminer quelles variables doivent être volatiles dans le code multithread?
Si vous pensez que tous les threads de lecture obtiennent toujours la dernière valeur d'une variable, vous devez marquer la variable comme volatile
Si vous avez un thread d'écriture pour modifier la valeur de la variable et plusieurs threads de lecture pour lire la valeur de la variable , le modificateur volatile garantit la cohérence de la mémoire.
Si vous avez plusieurs threads pour écrire et lire des variables, le modificateur volatile
seul ne garantit pas la cohérence de la mémoire. Vous devez synchronize
le code ou utiliser des constructions de haut niveau simultanéité comme Locks
, Concurrent Collections
, Atomic variables
etc.
Questions/articles SE connexes:
Explication des variables volatiles dans Java docs
Différence entre volatile et synchronisé en Java
javarevisited article
volatile
peut également être utilisé pour publier en toute sécurité des objets immuables dans un environnement multithread.
Déclarer un champ comme public volatile ImmutableObject foo
garantit que tous les threads voient toujours la référence d'instance actuellement disponible.
Voir Java Concurrency in Practice pour en savoir plus sur ce sujet.
En fait en désaccord avec l'exemple donné dans la réponse la plus votée, à ma connaissance, cela [~ # ~] pas [~ # ~] illustre correctement la sémantique volatile selon Java modèle de mémoire. Volatile a une sémantique bien plus complexe.
Dans l'exemple fourni, le thread principal peut continuer à imprimer "La température d'aujourd'hui est 0" pour toujours, même s'il existe un autre thread en cours d'exécution qui est censé mettre à jour la température si cet autre thread n'est jamais planifié.
Une meilleure façon d'illustrer la sémantique volatile est d'utiliser 2 variables.
Par souci de simplicité, nous supposerons que la seule façon de mettre à jour les deux variables est par la méthode "setTemperatures".
Par souci de simplicité, nous supposerons que seuls 2 threads sont en cours d'exécution, le thread principal et le thread 2.
//volatile variable
private static volatile int temperature;
//any other variable, could be volatile or not volatile doesnt matter.
private static int yesterdaysTemperature
//Called by other thread(s)
public static void setTemperatures(int temp, int yestemp){
//thread updates yesterday's temperature
yesterdaysTemperature = yestemp;
//thread updates today's temperature.
//This instruction can NOT be moved above the previous instruction for optimization.
temperature = temp;
}
les deux dernières instructions d'affectation peuvent [~ # ~] pas [~ # ~] être réorganisées à des fins d'optimisation par le compilateur, le runtime ou le matériel.
public static void main(String[] args) throws Exception{
while(true){
Thread.sleep(2000);
System.out.println("Today's temperature is "+temperature);
System.out.println("Yesterday's temperature was "+yesterdaysTemperature );
}
}
Une fois que le thread principal lit la température variable volatile (en cours d'impression),
1) Il y a une garantie qu'il verra la valeur la plus récente écrite de cette variable volatile quel que soit le nombre de threads qui y écrivent, quelle que soit la méthode dans laquelle ils la mettent à jour, synchronisée ou ne pas.
2) Si l'instruction system.out dans le thread principal s'exécute, après l'instant auquel le thread 2 a exécuté l'instruction temperature = temp, la température d'hier et la température d'aujourd'hui seront garantie d'imprimer les valeurs qui y sont définies par le thread 2 lors de l'exécution de l'instruction temperature = temp.
Cette situation obtient un lot [~ # ~] [~ # ~] plus complexe si a) plusieurs threads sont en cours d'exécution et b) Il existe d'autres méthodes que la méthode setTemperatures qui peuvent mettre à jour la variable la température d'hier et la température d'aujourd'hui qui sont activement appelées par ces autres fils. Je pense qu'il faudrait un article de taille décente pour analyser les implications en fonction de la façon dont le modèle de mémoire Java décrit la sémantique volatile.
En bref, tenter d'utiliser simplement volatile pour la synchronisation est extrêmement risqué, et vous feriez mieux de vous en tenir à la synchronisation de vos méthodes.
http://mindprod.com/jgloss/volatile.html
"Le mot-clé volatile est utilisé sur des variables qui peuvent être modifiées simultanément par d'autres threads."
"Étant donné que les autres threads ne peuvent pas voir les variables locales, il n'est jamais nécessaire de marquer les variables locales comme volatiles. Vous devez synchronisé pour coordonner les modifications apportées aux variables de différents threads, mais souvent volatile fera juste pour les regarder."
voltalie signifie continuer à changer la valeur. La valeur de cette variable ne sera jamais mise en cache localement: toutes les lectures et écritures iront directement dans la "mémoire principale". En d'autres termes Java et thread qui font ne pas mettre en cache la valeur de cette variable et la lire toujours dans la mémoire principale.