Différence entre volatile et synchronisé en Java

je me demande Quelle est la différence entre déclarer une variable comme volatile et toujours accéder à la variable dans un bloc synchronized(this) en Java?

selon cet article http://www.javamex.com/tutorials/synchronization_volatile.shtml il y a beaucoup à dire et il y a beaucoup de différences, mais aussi quelques similitudes.

je suis particulièrement intéressé par cette information:

...

  • l'accès à une variable volatile n'a jamais le potentiel de bloquer: nous ne faisons qu'une simple lecture ou écriture, donc contrairement à un bloc synchronisé, nous ne nous accrocherons jamais à AUCUNE serrure;
  • parce que l'accès à une variable volatile ne tient jamais une serrure, il n'est pas adapté pour les cas où nous voulons lire-mise à jour-écrire comme une opération atomique (à moins que nous sommes prêts à "manquer une mise à jour");

Que veulent-ils dire par lecture-mise à jour de l'écriture ? Une écriture n'est-elle pas aussi une mise à jour ou signifie-t-elle simplement que la mise à jour est une écriture qui dépend de la lecture?

la plupart du temps, quand est-il plus approprié de déclarer les variables volatile plutôt que d'y accéder par un bloc synchronized ? Est-ce une bonne idée d'utiliser volatile pour les variables qui dépendent de l'entrée? Pour par exemple, il y a une variable appelée render qui est lue à travers la boucle de rendu et définie par un événement de keypress?

188
demandé sur Albus Dumbledore 2010-08-19 11:34:44

5 réponses

il est important de comprendre qu'il y a deux aspects à la sécurité des fils.

  1. le contrôle de l'exécution, et
  2. la mémoire de la visibilité

le premier a à voir avec le contrôle quand le code exécute (y compris l'ordre dans lequel les instructions sont exécutées) et si elle peut exécuter simultanément, et le second à faire avec quand les effets en mémoire de ce qui a été fait sont visibles par les autres threads. Parce que chaque CPU a plusieurs niveaux de cache entre elle et la mémoire principale, les threads tournant sur des CPU ou des noyaux différents peuvent voir la "mémoire" différemment à n'importe quel moment donné dans le temps parce que les threads sont autorisés à obtenir et travailler sur des copies privées de la mémoire principale.

L'utilisation de synchronized empêche tout autre fil d'obtenir le moniteur (ou la serrure) pour le même objet , de ce fait empêcher tous les blocs de code protégés par synchronisation sur le même objet d'exécuter concurremment. Synchronisation aussi crée une barrière de mémoire "happens-before", provoquant une contrainte de visibilité de mémoire telle que tout ce qui est fait jusqu'au point certains thread libère une serrure apparaît à un autre thread par la suite d'acquérir la même serrure d'avoir eu lieu avant qu'il ait acquis la serrure. En termes pratiques, sur le matériel courant, cela provoque généralement le rinçage des caches CPU quand un moniteur est acquis et écrit à la mémoire principale quand il est libéré, les deux qui sont (relativement) coûteux.

en utilisant volatile , d'un autre côté, force tous les accès (lecture ou écriture) à la variable volatile à se produire à la mémoire principale, gardant effectivement la variable volatile hors des caches CPU. Cela peut être utile pour certaines actions où il est simplement nécessaire que la visibilité de la variable soit correcte et l'ordre des accès n'est pas important. L'utilisation de volatile modifie également le traitement de long et double pour exiger que les accès à eux soient atomiques; sur certains (plus anciens) matériel cela pourrait nécessiter des serrures, mais pas sur le matériel moderne 64 bits. Sous le nouveau modèle de mémoire (JSR-133) pour Java 5+, la sémantique de volatile a été renforcée pour être presque aussi forte que synchronisée en ce qui concerne visibilité de la mémoire et ordre des instructions (voir ) http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#volatile ). Pour des raisons de visibilité, chaque accès à un champ volatile agit comme une demi-synchronisation.

dans le nouveau modèle de mémoire, il est toujours vrai que les variables volatiles ne peuvent pas être ordonnées de nouveau les unes avec les autres. La différence est qu'il est maintenant plus facile de réorganiser champ normal les accès ils. L'écriture sur un champ volatile a le même effet mémoire qu'un moniteur, et la lecture sur un champ volatile a le même effet mémoire qu'un moniteur acquiert. En effet, parce que le nouveau modèle de mémoire impose des contraintes plus strictes sur la réorganisation des accès de champ volatiles avec d'autres accès de champ, volatiles ou non, tout ce qui était visible pour thread A quand il écrit au champ volatile f devient visible pour thread B quand il lit f .

-- JSR 133 (Java Memory Model) FAQ

ainsi, maintenant les deux formes de barrière de mémoire (sous la JMM actuelle) provoquent une barrière de nouvel ordre d'instruction qui empêche le compilateur ou l'exécution du temps de Nouvel Ordre des instructions à travers la barrière. Dans l'ancienne JMM, volatile n'a pas empêché la ré-commande. Cela peut être important, car en dehors des barrières de mémoire la seule limitation imposée est que, pour toute thread particulier , l'effet net du code est le même que si les instructions ont été exécutées exactement dans l'ordre dans lequel ils apparaissent dans la source.

une utilisation de volatile est pour un objet partagé mais immuable est recréé à la volée, avec de nombreux autres fils prenant une référence à l'objet à un point particulier de leur cycle d'exécution. L'un a besoin des autres fils pour commencer à utiliser l'objet recréé une fois qu'il est publié, mais ne j'ai besoin de la charge supplémentaire de synchronisation complète et c'est la tension et le rinçage de cache qui l'accompagnent.

// Declaration
public class SharedLocation {
    static public SomeObject someObject=new SomeObject(); // default object
    }

// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
//       someObject will be internally consistent for xxx(), a subsequent 
//       call to yyy() might be inconsistent with xxx() if the object was 
//       replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
private String getError() {
    SomeObject myCopy=SharedLocation.someObject; // gets current copy
    ...
    int cod=myCopy.getErrorCode();
    String txt=myCopy.getErrorText();
    return (cod+" - "+txt);
    }
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

en parlant à votre question read-update-write, en particulier. Considérez le code dangereux suivant:

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

maintenant, avec la méthode updateCounter () non synchronisée, deux threads peuvent y entrer en même temps. Parmi les nombreuses permutations de ce qui pourrait arriver, l'une est que thread-1 fait le test pour counter= = 1000 et trouve - ce vrai et est alors suspendu. Puis thread-2 fait le même test et voit aussi vrai et est suspendu. Puis thread-1 reprend et met le compteur à 0. Puis thread-2 reprend et remet counter à 0 parce qu'il a manqué la mise à jour de thread-1. Cela peut aussi se produire même si la commutation de thread ne se produit pas comme je l'ai décrit, mais simplement parce que deux copies de counter mises en cache différentes étaient présentes dans deux cœurs CPU différents et les threads chacun couru sur un noyau séparé. D'ailleurs, l'un thread pourrait avoir le compteur à une valeur et l'autre pourrait avoir le compteur à une valeur entièrement différente juste à cause de la mise en cache.

ce qui est important dans cet exemple, c'est que la variable counter a été lue de la mémoire principale dans le cache, mise à jour dans le cache et écrite seulement de nouveau à la mémoire principale à un moment indéterminé plus tard quand une barrière de mémoire s'est produite ou quand la mémoire cache était nécessaire pour quelque chose d'autre. Faire le compteur volatile est insuffisant pour la sécurité des fils de ce code, parce que l'essai pour le maximum et les assignations sont des opérations discrètes, y compris l'incrément qui est un ensemble de non-atomiques read+increment+write instructions machine, quelque chose comme:

MOV EAX,counter
INC EAX
MOV counter,EAX

les variables volatiles ne sont utiles que lorsque toutes les opérations effectuées sur elles sont" atomiques", comme mon exemple où une référence à un objet entièrement formé est seulement lue ou écrit (et, en fait, typiquement, il est seulement écrit à partir d'un point unique). Un autre exemple serait une référence de tableau volatile supportant une liste de copie en écriture, à condition que le tableau ne soit lu qu'en prenant d'abord une copie locale de la référence.

339
répondu Lawrence Dol 2018-07-25 08:40:12

volatile est un modificateur de champ , tandis que synchronisé modifie des blocs de code et méthodes . Nous pouvons donc spécifier trois variantes d'un accesseur simple en utilisant ces deux mots clés:

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1() accède à la valeur actuellement stockée dans i1 dans le thread courant. Les Threads peuvent avoir copies locales des variables, et les données ne doivent pas être les mêmes que les données contenues dans les autres threads.En particulier, un autre thread peut avoir mis à jour i1 dans son thread, mais la valeur dans le thread courant peut être différente de cette valeur mise à jour. En fait, Java a l'idée d'une mémoire" principale", et c'est la mémoire qui contient la valeur" correcte " actuelle pour les variables. Les Threads peuvent avoir leur propre copie de données pour les variables, et la copie de thread peut être différente de la mémoire "principale". Donc en fait, il est possible que la mémoire "principale "ait une valeur de 1 pour i1 , pour thread1 d'avoir une valeur de 2 pour i1 et pour thread2 d'avoir une valeur de 3 pour i1 si thread1 et thread2 ont à la fois mis à jour i1 mais ceux valeur mis à jour n'a pas encore été propagé à la mémoire "principale" ou d'autres fils.

par contre, geti2() accède effectivement à la valeur de i2 à partir de la mémoire" principale". Une variable volatile n'est pas autorisée à avoir une copie locale d'une variable qui est différente de la valeur actuellement conservée dans la mémoire "principale". Effectivement, une variable déclarée volatile doit avoir ses données synchronisées à travers tous les threads, de sorte que chaque fois que vous accédez ou mettez à jour la variable dans un thread, tous les autres threads voient immédiatement la même valeur. Généralement les variables volatiles ont un accès et une mise à jour plus élevés que les variables "simples". Généralement, les threads sont autorisés à avoir leur propre copie de données, pour une meilleure efficacité.

il y a deux différences entre volitile et synchronisé.

tout d'abord synchronisé obtient et libère des verrous sur les moniteurs qui ne peuvent forcer qu'un seul thread à la fois pour exécuter un bloc de code. C'est l'aspect assez bien connu à synchroniser. Mais synchronisé synchronise aussi la mémoire. En fait synchronisé synchronise l'ensemble de la mémoire de fil avec la mémoire "principale". Ainsi l'exécution geti3() fait ce qui suit:

  1. le fil acquiert la serrure sur le moniteur pour l'objet ceci .
  2. la mémoire thread élimine toutes ses variables, c'est-à-dire qu'elle a toutes ses variables efficacement lues à partir de la mémoire" principale".
  3. le bloc de code est exécuté (dans ce cas, le réglage du retour valeur de la valeur courante de i3, qui peut avoir été réinitialisée à partir de la mémoire "principale").
  4. (tout changement aux variables serait normalement maintenant écrit dans la mémoire" principale", mais pour geti3 () nous n'avons aucun changement.)
  5. Le thread libère le verrou sur le moniteur de l'objet.

donc où volatile synchronise seulement la valeur d'une variable entre la mémoire de thread et la mémoire" principale", synchronise la valeur de toutes les variables entre la mémoire thread et la mémoire "principale", et verrouille et libère un moniteur pour démarrer. Clairement synchronisé est susceptible d'avoir plus de ressources que les volatiles.

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

91
répondu Kerem Baydoğan 2016-08-06 09:47:09

synchronized est au niveau de la méthode/bloquer niveau de restriction d'accès de modification. Il s'assurera qu'un thread possède la serrure de la section critique. Seul le thread, qui possède une serrure peut entrer dans le bloc synchronized . Si d'autres threads essaient d'accéder à cette section critique, ils doivent attendre que le propriétaire actuel libère la serrure.

volatile est un modificateur d'accès variable qui force tous les threads à obtenir la dernière valeur de la variable à partir de la mémoire principale. Pas de verrouillage est nécessaire pour accéder aux variables volatile . Tous les threads peuvent accéder à la valeur variable volatile en même temps.

un bon exemple d'utilisation de la variable volatile: Date variable.

supposons que vous avez fait la variable de Date volatile . Tous les threads, qui accèdent à cette variable obtiennent toujours les dernières données de la mémoire principale de sorte que tous les threads montrent la valeur réelle (actuelle) Date. Vous n'avez pas besoin de différents threads montrant l'heure différente pour la même variable. Tous les threads doivent afficher la Date exacte.

enter image description here

regardez cet" article pour une meilleure compréhension du concept volatile .

Lawrence Dol cleary a expliqué votre read-write-update query .

concernant vos autres requêtes

quand est-il plus approprié de déclarer des variables volatiles que d'y accéder par le biais de la synchronisation?

vous devez utiliser volatile si vous pensez que tous les threads devraient obtenir la valeur réelle de la variable en temps réel comme l'exemple que j'ai expliqué pour la variable Date.

Est-ce une bonne idée d'utiliser la volatilité des variables qui dépendent d'entrée?

Réponse sera la même que dans la première requête.

se Réfèrent à cette article pour une meilleure compréhension.

15
répondu Ravindra babu 2016-05-29 07:08:02
  1. volatile le mot-clé en java est un modificateur de champ, tandis que synchronized modifie les blocs de code et les méthodes.

  2. synchronized obtient et communiqués de verrouillage de l'écran de java volatile mot-clé n'est pas l'exiger.

  3. les Threads en Java peuvent être bloqués pour attendre n'importe quel moniteur dans le cas de synchronized , ce qui n'est pas le cas avec volatile mot-clé en Java.

  4. synchronized la méthode affecte la performance plus que le mot-clé volatile en Java.

  5. depuis volatile mot-clé en Java synchronise seulement la valeur d'une variable entre la mémoire de Thread et la mémoire "principale" tandis que synchronized mot-clé synchronise la valeur de toutes les variables entre la mémoire de thread et la mémoire "principale" et verrouille et libère un moniteur pour démarrer. Pour cette raison, synchronized mot-clé en Java est susceptible d'avoir plus de frais généraux que volatile .

  6. vous ne pouvez pas synchroniser sur null object mais votre variable volatile en java pourrait être null.

  7. de Java 5 L'écriture dans un champ volatile a le même effet de mémoire qu'une sortie de moniteur, et la lecture d'un champ volatile a le même effet de mémoire qu'un moniteur acquérir

6
répondu shraddha bhardwaj 2016-08-14 18:02:07

j'aime jenkov explication

visibilité des objets partagés

si deux ou plusieurs threads partagent un objet, sans l'utilisation appropriée des déclarations volatile ou synchronisation , les mises à jour de l'objet partagé effectuées par un thread peuvent ne pas être visibles par les autres threads.

Imaginez que l'objet partagé est initialement stocké dans la mémoire principale. Un thread tournant sur CPU one lit alors l'objet partagé dans son cache CPU. Là il fait un changement de l'objet partagé. Tant que le cache CPU n'a pas été vidé de sa mémoire principale, la version modifiée de l'objet partagé n'est pas visible par les threads tournant sur D'autres CPU. De cette façon, chaque thread peut finir avec sa propre copie de l'objet partagé, chaque copie étant placée dans un cache CPU différent.

le diagramme suivant illustre esquissé la situation. Un fil courant sur le CPU de gauche copie l'objet partagé dans son cache CPU, et change sa variable count en 2. Ce changement n'est pas visible pour les autres threads qui tournent sur le CPU droit, parce que la mise à jour pour compter n'a pas encore été renvoyée à la mémoire principale.

enter image description here

pour résoudre ce problème, vous pouvez utiliser le mot clé volatile de Java . Le mot-clé volatile peut s'assurer qu'une variable donnée est lue directement à partir de la mémoire principale, et toujours écrite de nouveau à la mémoire principale lors de la mise à jour.

Conditions De Course

si deux ou plusieurs threads partagent un objet, et plus d'un thread met à jour des variables dans cet objet partagé, des conditions de course peuvent se produire.

imaginez si thread a lit la variable count d'un objet partagé dans son cache CPU. Imaginez aussi, ce thread B fait la même chose, mais dans un cache CPU différent. Maintenant thread a ajoute un à compter, et thread B fait la même chose. Maintenant var1 a été incrémenté deux fois, une fois dans chaque cache CPU.

si ces incréments avaient été effectués de façon séquentielle, la variable count aurait été incrémentée deux fois et si la valeur originale + 2 avait été réécrite à la mémoire principale.

Toutefois, les deux tranches ont été réalisées simultanément sans synchronisation. Quel que soit le thread A et B qui écrit sa version mise à jour de count back to main memory, la valeur mise à jour sera seulement 1 plus élevée que la valeur originale, malgré les deux incréments.

ce diagramme illustre une occurrence du problème avec les conditions de course comme décrit ci-dessus:

enter image description here

Pour résoudre ce problème, vous pouvez utiliser un Java bloc synchronisé . Un bloc synchronisé garantit qu'un seul thread peut entrer une section critique donnée du code à tout moment. Les blocs synchronisés garantissent également que toutes les variables accédées à l'intérieur du bloc synchronisé seront lues à partir de la mémoire principale, et que lorsque le thread sort du bloc synchronisé, toutes les variables mises à jour seront à nouveau renvoyées à la mémoire principale, que la variable soit déclarée volatile ou non.

1
répondu yoAlex5 2018-06-16 14:06:51