9
votes

La réinitialisation d'un objet dans un concours est-elle causée par une relation de mémoire "arrive-avant"?

Je travaille avec le code existant possède une boutique d'objet sous la forme d'un Concourshashmap. Dans la carte sont des objets mutables stockés, utilisez plusieurs threads. Pas de deux threads essaie de modifier un objet à la fois par conception. Mon inquiétude concerne la visibilité des modifications entre les threads.

Actuellement, le code des objets a la synchronisation sur les "Setteurs" (gardé par l'objet lui-même). Il n'y a pas de synchronisation sur les "getters" ni les membres volatils. Cela signifie que cette visibilité n'est pas garantie. Toutefois, lorsqu'un objet est modifié, il est Retour à nouveau sur la carte (la méthode Met () est appelée à nouveau, la même clé). Cela signifie-t-il que lorsqu'un autre thread tire l'objet de la carte, il verra les modifications?

J'ai recherché ici ici sur Stackoverflow, dans JCIP et dans la description du package pour Java.Util. concurrent. Je pense essentiellement que je pense que je pense ... mais la paille finale qui m'a amené à poser à cette question provenait de la description du colis, il indique:

actions dans un fil avant de placer un objet dans une collection simultanée se produisent - avant les actions postérieures à l'accès ou à l'élimination de cet élément de la collection dans un autre fil.

par rapport à ma question, les "actions" incluent les modifications des objets stockés sur la carte avant le re-mis ()? Si tout cela entraîne une visibilité à travers les threads, est-ce une approche efficace? Je suis relativement nouveau dans les threads et j'apprécierais vos commentaires.

EDIT:

Merci à tous pour vos réponses! C'était ma première question sur Stackoverflow et cela m'a été très utile.

Je dois aller avec réponse de Ptomli parce que je pense que cela abordait le plus clairement ma confusion. À savoir, l'établissement d'une relation "arrive" avant "n'affecte pas nécessairement la visibilité de modification dans ce cas. Ma "question de titre" est mal construite concernant ma question réelle décrite dans le texte. Réponse de ptomli de la réponse de ptomli maintenant Jives avec ce que j'ai lu JCIP : "Pour que tous les threads voient les valeurs les plus récentes des variables mutables partagées, les filets de lecture et d'écriture doivent se synchroniser sur une serrure commune" (page 37). Re-mettre l'objet dans la carte ne fournit pas cette serrure commune pour la modification aux membres de l'objet inséré.

J'apprécie tous les conseils pour le changement (objets immuables, etc.), et je suis tout à fait concrété. Mais pour cette affaire, comme je l'ai mentionné, il n'y a pas de modification simultanée en raison d'une manipulation minutieuse du fil. Un thread modifie un objet, et un autre thread lit ultérieurement l'objet (avec le transporteur de la CHM étant le convoyeur d'objet). Je pense que le CHM est insuffisant pour que le fil d'exécution ultérieure puisse voir les modifications de la première étant donné la situation que j'ai fournie. Cependant, je pense que beaucoup d'entre vous ont correctement répondu à la question du titre .


1 commentaires

Il le fait, il verrouille toute modification.


6 Réponses :


4
votes

Je pense que votre question concerne davantage les objets que vous rangez sur la carte et comment ils réagissent à l'accès simultané que la carte simultanée elle-même.

Si les instances que vous stockez sur la carte ont des mutateurs synchronisés, mais pas des accesseurs synchronisés, je ne vois pas comment ils peuvent être du thread en toute sécurité comme décrit.

Prenez la carte hors de l'équation et déterminez si les instances que vous stockez sont sur elles-mêmes.

Toutefois, lorsqu'un objet est modifié, il est re-remis dans la carte (la méthode Met () est appelée à nouveau, la même clé). Cela signifie-t-il que lorsqu'un autre thread tire l'objet de la carte, il verra les modifications?

Cela illustre la confusion. L'instance qui est ré-mise sur la carte sera extraite de la carte par un autre fil. C'est la garantie de la carte simultanée. Cela n'a rien à voir avec la visibilité de l'état de l'instance stockée elle-même.


4 commentaires

Peut-être que je demande quelque chose de stupide, mais pourquoi le re-mis? L'autre thread est-il en train de supprimer cette instance?


@Mister Smith Le re-Met est de déclencher un événement - avant (rincer le cache) afin que tous les threads voient la valeur mise à jour. Cependant, je ne pense pas que cela fonctionne bien comme prévu (voir ma réponse).


@ toto2 logique bizarre que vous utilisez là-bas. Le même exemple est remis dans la même clé. La carte ne peut avoir aucun effet sur la visibilité du changement d'état de l'instance stockée. Les garanties simultanées faites par la carte sont seulement que l'instance correcte est renvoyée d'un get (par exemple). Comme je l'ai signalé, la question OP concerne vraiment la sécurité du fil des instances stockées. La carte n'est qu'une diversion. Pourquoi les auteurs pensaient ils re-mettaient? Qui sait, mais ne fait probablement pas ce qu'ils pensent.


@ptomli je suis en désaccord avec votre deuxième phrase. Dans le dernier modèle de mémoire Java, lorsqu'il y a un «se passe-au-delà», tout est mis à jour. Néanmoins, la procédure de synchronisation est toujours imparfaite même si tout est mis à jour, comme j'explique dans ma réponse.



3
votes

Ma compréhension est que cela devrait fonctionner pour que tous deviennent après le ré-mis, mais ce serait une méthode de synchronisation très dangereuse.

Que se passe-t-il que cela se passe avant le re-mis, mais alors que des modifications se produisent. Ils ne voient peut-être que quelques-uns des changements et l'objet aurait un état incohérent.

Si vous le pouvez, je vous recommanderais de stocker des objets immutables sur la carte. Ensuite, tout get récupérera une version de l'objet qui était à jour quand il a fait le get.


0 commentaires

7
votes

Vous appelez concurrhashmap.put après chaque écriture à un objet. Cependant, vous n'avez pas précisé que vous appelez également Concurrashmap.get avant chaque lecture. Ceci est nécessaire.

Ceci est vrai de toutes les formes de synchronisation: vous devez avoir des "points de contrôle" dans les deux threads. Synchronisation Un seul thread est inutile.

Je n'ai pas vérifié le code source de Concurrenthashmap pour vous assurer que mettre et obtenez déclencher un événement auparavant, mais il n'est que logique qu'ils devraient. < / p>

Il y a toujours un problème avec votre méthode cependant, même si vous utilisez les deux mettre et obtenir . Le problème se produit lorsque vous modifiez un objet et qu'il est utilisé (dans un état incohérent) par l'autre thread avant qu'il ne soit Mettez . C'est un problème subtil parce que vous pourriez penser que l'ancienne valeur serait lue car elle n'a pas été Mettre et cela ne causerait pas de problème. Le problème est que lorsque vous ne synchronisez pas, vous n'êtes pas garanti d'obtenir un objet plus âgé cohérent, mais le comportement est indéfini. Le JVM peut mettre à jour la partie de l'objet dans les autres threads, à tout moment. Ce n'est que lorsque vous utilisez une synchronisation explicite que vous êtes sûr que vous mettez à jour les valeurs de manière cohérente à travers les threads.

Qu'est-ce que vous pouviez faire:
(1) Synchronisez tous les accès (getters et setters) à vos objets partout dans le code. Soyez prudent avec les Setters: Assurez-vous que vous ne pouvez pas définir l'objet dans un état incohérent. Par exemple, lors de la définition du prénom et du nom de famille, il n'est pas suffisant de disposer de deux sigres synchronisés: vous devez obtenir le verrouillage de l'objet pour les deux opérations ensemble.
ou de
(2) Lorsque vous mettre un objet sur la carte, placez une copie profonde au lieu de l'objet lui-même. De cette façon, les autres threads ne liront jamais un objet dans un état incohérent.

Modifier :
Je viens de remarquer

Actuellement, le code des objets a la synchronisation sur les "Setteurs" (gardé par l'objet lui-même). Il n'y a pas de synchronisation sur le "getters" et les membres ne sont pas volatils.

Ce n'est pas bon. Comme je l'ai dit ci-dessus, la synchronisation sur un seul thread n'est pas une synchronisation du tout. Vous pouvez synchroniser sur tous vos threads de votre écrivain, mais qui s'en soucie depuis que les lecteurs n'obtiendront pas les bonnes valeurs.


0 commentaires

1
votes

oui, mettre entraîne une écriture volatile, même si la valeur de la clé existe déjà sur la carte.

Utiliser ConcourshashMap pour publier des objets à travers le thread est assez efficace. Les objets ne doivent pas être modifiés plus loin une fois qu'ils sont sur la carte. (Ils ne doivent pas nécessairement être strictement immuables (avec champs finaux))


1 commentaires

Je pense que son code modifie les objets après qu'ils soient initialement mis sur la carte. Ma réponse était de mettre des copies profondes des objets sur la carte, de sorte que ceux-ci ne seront pas modifiés.



2
votes

C'est un extrait de code de Java. util.concurrent.concurthashmap (Ouvrir JDK 7): xxx

dangereux.getobjectevolatile () est documenté comme getter avec interne volatile Sémantique, la barrière mémoire sera donc croisée lors de la référence.


0 commentaires

5
votes

Je pense que cela a déjà été dit sur quelques réponses mais pour résumer le montant

si votre code va

  • chm # obtenir
  • Appelez divers Setters
  • chm # mettre

    Ensuite, le "arrive-avant" fourni par le MET garantira que tous les appels de mutate sont exécutés avant la mise en place. Cela signifie que toute utilisation ultérieure sera garantie de voir ces changements.

    Votre problème est que l'état actuel de l'objet ne sera pas déterministe car si le flux réel des événements est

    • fil 1: chm # obtenir
    • thread 1: Setter d'appel
    • thread 2: chm # obtenir
    • thread 1: Setter d'appel
    • thread 1: Setter d'appel
    • Fil 1: CHM # Mettez

      Il n'y a pas de garantie sur ce que l'état de l'objet sera dans le fil 2. Cela pourrait voir l'objet avec la valeur fournie par le premier setter ou non.

      La copie immuable serait la meilleure approche car alors uniquement des objets complètement cohérents sont publiés. Rendre les différentes régleurs synchronisées (ou les références sous-jacentes volatiles) ne vous permettent toujours pas de publier un état cohérent, cela signifie simplement que l'objet verra toujours la valeur la plus récente pour chaque getter sur chaque appel.


0 commentaires