2010-06-23 3 views
10

Dans une belle article with some concurrency tips, un exemple a été optimisé pour les lignes suivantes:Différence entre la synchronisation de champ lit et volatile

double getBalance() { 
    Account acct = verify(name, password); 
    synchronized(acct) { return acct.balance; } 
} 

Si je comprends bien, le point de la synchronisation est de faire en sorte que la valeur de acct.balance qui sont lus par ce fil est courant et que toutes les écritures en attente aux champs de l'objet dans acct.balance sont aussi écrites dans la mémoire principale.

L'exemple m'a fait réfléchir un peu: ne serait-il pas plus efficace de déclarer simplement acct.balance (c'est-à-dire le solde de la classe Account) volatile? Il devrait être plus efficace, vous sauver tous les synchronize sur les accès à acct.balance et ne verrouillerait pas l'objet entier acct. Est-ce que je manque quelque chose?

+0

Vous avez raison, mais l'article est vraiment quelque chose de complètement différent - la réduction de la portée de verrouillage. – gustafc

Répondre

13

Vous avez raison. volatile fournit une garantie de visibilité. synchronized fournit à la fois une garantie de visibilité ET une sérialisation des sections de code protégées. Pour des situations TRES simples, volatile est suffisant, mais il est facile d'avoir des problèmes en utilisant volatile au lieu de la synchronisation.

Si vous deviez supposer que le compte a une façon d'ajuster son équilibre alors volatile n'est pas assez bon

public void add(double amount) 
{ 
    balance = balance + amount; 
} 

Ensuite, nous avons un problème si l'équilibre est instable sans autre synchronisation. Si deux fils devaient essayer de téléphoner ajouter() ensemble, vous pourriez avoir un « raté » mise à jour où les événements suivants se

Thread1 - Calls add(100) 
Thread2 - Calls add(200) 
Thread1 - Read balance (0) 
Thread2 - Read balance (0) 
Thread1 - Compute new balance (0+100=100) 
Thread2 - Compute new balance (0+200=200) 
Thread1 - Write balance = 100 
Thread2 - Write balance = 200 (WRONG!) 

Il est évident que cela est faux parce que les deux threads lisent la valeur actuelle et mis à jour de manière indépendante puis écrit en arrière (lire, calculer, écrire). volatile n'aide pas ici, donc vous auriez besoin synchronisé pour s'assurer qu'un thread a terminé la mise à jour entière avant que l'autre thread a commencé. Je trouve généralement que si j'écris du code, je pense que je peux utiliser volatile au lieu de synchronisé, la réponse pourrait bien être oui, mais le temps et l'effort pour le comprendre et le danger de se tromper ne vaut pas le bénéfice (performance mineure). En outre, une classe Account bien écrite traiterait toute la logique de synchronisation en interne afin que les appelants n'aient pas à s'en soucier.

1

Déclarant compte aussi volatil est soumis à des restrictions et les questions suivantes

1. "Comme d'autres threads ne peuvent pas voir les variables locales, les variables locales déclarant volatile est futile." De plus Si vous essayez de déclarer une variable volatile dans une méthode, vous obtiendrez une erreur de compilation dans certains cas.

double getBalance() { Compte volatile acct = vérifier (nom, mot de passe); // incorrect .. }

  1. Déclarant compte que volatile met en garde contre le compilateur à les chercher frais à chaque fois, plutôt que de les mettre en cache dans les registres. Cela aussi inhibe certaines optimisations qui supposent qu'aucun autre thread ne changera les valeurs de façon inattendue.

  2. Si vous avez besoin synchronisé pour coordonner les modifications apportées aux variables de threads différents, volatile ne détient pas vous garantir l'accès atomique, parce que l'accès à une variable volatile jamais un verrou, il ne convient pas pour les cas où l'on veut lire-mettre à jour-écrire comme une opération atomique. Sauf si vous êtes sûr que acct = vérifier (nom, mot de passe); est une opération atomique unique, vous ne pouvez pas garantir les résultats exceptés

  3. Si la variable acct est une référence d'objet, alors il est probable qu'elle soit nulle. La tentative de synchronisation sur un objet NULL lancera une exception NullPointerException en utilisant synchronized. (parce que vous êtes synchroniser efficacement sur la référence, et non l'objet réel) Lorsque, volatil ne se plaint pas

  4. vous pourriez plutôt déclarer une variable booléenne comme volatile comme ici

    volatile someAccountflag private boolean;

    public void getBalance() { Compte compte; while (! SomeAccountflag) { acct = vérifier (nom, mot de passe); } }

Notez que vous ne pouvez pas déclarer someAccountflag comme synchronisé, comme vous ne pouvez pas synchroniser sur une primitive avec synchronisation, synchronisés fonctionne uniquement avec des variables d'objet, alors que la variable primitive ou un objet peut être déclaré volatile

6. Les champs statiques finaux de classe n'ont pas besoin d'être volatils, JVM s'occupe de ce problème. Donc le someAccountflag n'a même pas besoin d'être déclaré volatile s'il s'agit d'un statique final ou vous pouvez utiliser l'initialisation paresseuse de singleton en créant un compte comme un objet singleton et le déclarer comme suit: private final static AccountSingleton acc_singleton = new AccountSingleton();

+0

Merci pour la réponse, mais j'ai effectivement suggéré de ne pas déclarer acct volatile, mais acct.balance - c'est-à-dire, le solde sur le terrain de la classe compte. Cela modifie certains de vos commentaires. J'ai essayé de clarifier un peu la question à cet égard. –

1

Si plusieurs threads modifient et accèdent aux données, synchronized garantit la cohérence des données entre plusieurs threads.

Si un thread unique modifie les données et que plusieurs threads tentent de lire la dernière valeur de données, utilisez la construction volatile.

Mais pour le code ci-dessus, volatile ne garantit pas la cohérence de la mémoire si plusieurs tractions modifient l'équilibre. AtomicReference avec le type Double sert votre but.

question connexe SE:

Difference between volatile and synchronized in Java

Questions connexes