web-dev-qa-db-fra.com

Java: rendre tous les champs finaux ou volatils?

Si j'ai un objet qui est partagé entre les threads, il me semble que chaque champ doit être soit final ou volatile, avec le raisonnement suivant:

  • si le champ doit être modifié (pointer vers un autre objet, mettre à jour la valeur primitive), alors le champ doit être volatile pour que tous les autres threads opèrent sur la nouvelle valeur. Une simple synchronisation sur les méthodes qui accèdent audit champ est insuffisante car elles pourraient renvoyer une valeur mise en cache.

  • si le champ ne doit jamais changer, faites-le final.

Cependant, je n'ai rien trouvé à ce sujet, alors je me demande si cette logique est défectueuse ou tout simplement trop évidente?

[~ # ~] éditez [~ # ~] bien sûr au lieu de volatile on pourrait utiliser un final AtomicReference ou similaire.

[~ # ~] éditez [~ # ~] par exemple, voir La méthode getter est-elle une alternative à volatile en Java?

[~ # ~] éditez [~ # ~] pour éviter les confusions: Cette question concerne l'invalidation du cache! Si deux threads opèrent sur le même objet, les champs des objets peuvent être mis en cache (par thread), s'ils ne sont pas déclarés volatils. Comment puis-je garantir que le cache est correctement invalidé?

FINAL EDIT Merci à @Peter Lawrey qui m'a indiqué JLS §17 (modèle de mémoire Java). Pour autant que je vois, il indique que la synchronisation établit une relation passe-avant entre les opérations, de sorte qu'un thread voit les mises à jour d'un autre thread si ces mises à jour se sont produites avant, par exemple si getter et setter pour un champ non volatile sont synchronized.

23
Moritz

Bien que je pense que private final Aurait probablement dû être la valeur par défaut pour les champs et les variables avec un mot-clé comme var le rendant mutable, utiliser volatile lorsque vous n'en avez pas besoin est

  • beaucoup plus lent, souvent environ 10 fois plus lent.
  • ne vous donne généralement pas la sécurité dont vous avez besoin, mais peut rendre la recherche de ces bogues plus difficile en les rendant moins susceptibles d'apparaître.
  • contrairement à final qui améliore la clarté en disant que cela ne devrait pas être modifié, utiliser volatile quand il n'est pas nécessaire, est susceptible de prêter à confusion car le lecteur essaie de comprendre pourquoi il a été rendu volatil.

si le champ doit être modifié (pointer vers un autre objet, mettre à jour la valeur primitive), alors le champ doit être volatile pour que tous les autres threads opèrent sur la nouvelle valeur.

Bien que cela soit bien pour les lectures, considérez ce cas trivial.

volatile int x;

x++;

Ce n'est pas thread-safe. Comme c'est la même chose que

int x2 = x;
x2 = x2 + 1; // multiple threads could be executing on the same value at this point.
x = x2;

Le pire est que l'utilisation de volatile rendrait ce type de bogue plus difficile à trouver.

Comme le souligne yshavit, la mise à jour de plusieurs champs est plus difficile à contourner avec volatile par exemple HashMap.put(a, b) met à jour plusieurs références.

Une simple synchronisation sur les méthodes qui accèdent audit champ est insuffisante car elles pourraient renvoyer une valeur mise en cache.

synchronized vous offre toutes les garanties de mémoire de volatile et plus, c'est pourquoi il est considérablement plus lent.

REMARQUE: juste synchronized- chaque méthode n'est pas toujours suffisante non plus. StringBuffer a toutes les méthodes synchronisées mais est pire qu'inutile dans un contexte multi-thread car son utilisation est susceptible d'être source d'erreurs.

Il est trop facile de supposer que la sécurité du fil est comme saupoudrer de poussière de fée, ajoutez un peu de sécurité au fil magique et vos bugs disparaissent. Le problème est que la sécurité du fil ressemble plus à un seau avec de nombreux trous. Bouchez les plus gros trous et les bugs peuvent sembler disparaître, mais à moins de tous les boucher, vous n'avez pas la sécurité des threads, mais cela peut être plus difficile à trouver.

En termes de synchronisation vs volatile, cela indique

D'autres mécanismes, tels que les lectures et les écritures de variables volatiles et l'utilisation de classes dans le package Java.util.concurrent, offrent d'autres moyens de synchronisation.

https://docs.Oracle.com/javase/specs/jls/se7/html/jls-17.html

31
Peter Lawrey

Faire des champs que vous n'avez pas besoin de modifier final est une bonne idée, indépendamment des problèmes de threading. Cela rend les instances de la classe plus faciles à raisonner, car vous pouvez savoir plus facilement dans quel état elles se trouvent.

En termes de rendre les autres champs volatile:

Une simple synchronisation sur les méthodes qui accèdent audit champ est insuffisante car elles pourraient renvoyer une valeur mise en cache.

Vous ne verriez une valeur en cache que si vous accédez à la valeur en dehors d'un bloc synchronisé.

Tous les accès devraient être correctement synchronisés. La fin d'un bloc synchronisé est garantie avant le début d'un autre bloc synchronisé (lors de la synchronisation sur le même moniteur).

Il y a au moins quelques cas où vous auriez encore besoin d'utiliser la synchronisation:

  • Vous voudriez utiliser la synchronisation si vous deviez lire puis mettre à jour un ou plusieurs champs atomiquement.
    • Vous pourrez peut-être éviter la synchronisation pour certaines mises à jour de champ unique, par exemple si vous pouvez utiliser un Atomic* classe au lieu d'un "ancien champ simple"; mais même pour une seule mise à jour de champ, vous pouvez toujours avoir besoin d'un accès exclusif (par exemple, ajouter un élément à une liste tout en supprimant un autre).
  • De plus, volatile/final peut être insuffisant pour les valeurs non thread-safe, comme un ArrayList ou un tableau.
8
Andy Turner

Si un objet est partagé entre des threads, vous avez deux options claires:

1. Rendre cet objet en lecture seule

Les mises à jour (ou cache) n'ont donc aucun impact.

2. Synchroniser sur l'objet lui-même

L'invalidation du cache est difficile. Très difficile. Donc, si vous ne devez garantir aucune valeur périmée, vous devez protéger cette valeur et protéger le verrou autour de cette valeur.

Rendez le verrou et les valeurs privés sur l'objet partagé, les opérations sont donc un détail d'implémentation.

Pour éviter les verrous morts, ces opérations doivent être aussi "atomiques" que pour éviter d'interagir avec les autres verrous.

1
André LFS Bacci