12
votes

Synchronisation correcte des égaux () en Java

J'ai la classe suivante qui contient un seul champ i . L'accès à ce champ est gardé par la serrure de l'objet ("this"). Lors de la mise en œuvre égale (), j'ai besoin de verrouiller cette instance (a) et l'autre (b). Si thread 1 appelle A.Equals (b) et au même moment fileter 2 appels B.Qui (a), l'ordre de verrouillage est inverse dans les deux implémentations et peut entraîner une impasse.

Comment puis-je mettre en œuvre des égaux () Pour une classe qui a des champs synchronisés? xxx


0 commentaires

14 Réponses :


0
votes

La bonne implémentation de est égale () et hashcode () est requis par diverses choses telles que les structures de données de hachage, et donc vous n'avez donc aucune option réelle. D'une autre perspective, égale () et hashcode () ne sont que des méthodes, avec les mêmes exigences sur la synchronisation que d'autres méthodes. Vous avez toujours le problème de l'impasse, mais ce n'est pas spécifique au fait que est égal () qui le cause.


0 commentaires

7
votes

Pourquoi synchroniser? Quel est le cas d'utilisation où il est important que cela puisse changer pendant la comparaison et que cela n'a pas d'importance si si elle change immédiatement après avant le code en fonction des courses d'égalité. (c.-à-d. Si vous avez du code en fonction de l'égalité, que se passe-t-il si les valeurs deviennent inégales avant ou pendant ce code)

Je pense que vous devez jeter un coup d'œil au processus plus large pour voir où vous devez verrouiller.


3 commentaires

Vous êtes correct qu'une valeur pourrait changer immédiatement après la comparaison, mais lorsque la valeur enveloppée est plus intéressante qu'une primitive int , il existe une possibilité de cette valeur dans un état incohérent, sauf si elle est synchronisée pendant que ses parties sont examinés.


Vrai mais vous avez toujours le problème de quoi faire après les égaux


En fait, la question est une visibilité. Sans utiliser synchronisé, un ordre valide pourrait toujours entraîner un résultat inattendu (c'est-à-dire une valeur définie plus tôt, n'est pas visible quand elle devrait être).



1
votes

Toujours les enfermer dans le même ordre, une façon dont vous pouvez décider de la commande est sur les résultats de système.identityhashcode (objet)

Modifier pour inclure Commentaire:

La meilleure solution pour traiter le cas rare des codes identitaires étant égaux nécessite plus de détails sur ce que l'autre verrouillage de ces objets se passe.

Tous les exigences de verrouillage de plusieurs objets multiples doivent utiliser le même processus de résolution.

Vous pouvez créer un utilitaire partagé pour suivre des objets avec le même IdentityHashCode pour la courte période des exigences de verrouillage et fournir une commande répétable pour eux pour la période de suivie.


1 commentaires

Boolean public est égal (objet obj) {si (this == obj) retourne vrai; Si (obj == null) renvoie false; Si (getclass ()! = obj.getclass ()) renvoie false; Synchroniser autre = (synchroniser) obj; boolean plus petithash = système.ididentityhashcode (this)



9
votes

essayer de synchroniser égale et hashcode à l'intérieur de l'objet ne fonctionnera pas correctement. Considérez le cas d'un hashmap qui utilise hashcode pour découvrir quel objet "godet" un objet sera dans, puis utilise égale sur la recherche de manière séquentielle objets dans le seau.

Si les objets sont autorisés à muter de manière à modifier les résultats de hashcode ou égal vous pouvez vous retrouver avec un scénario où hashmap Appels hashcode . Il acquiert la serrure, obtient le hachage et libère la serrure. hashmap puis procède pour calculer le "godet" à utiliser. Mais avant hashmap peut acquérir le verrouillage sur une autre personne d'autre attrape la serrure et la mutation de l'objet de sorte que est égal à devenir incompatible avec la valeur précédente de hashcode . Cela conduira à des résultats catastrophiques.

Le hashcode et est utilisé dans de nombreux endroits et est noyau de l'API de collections Java. J'aurais peut-être une valeur précieuse pour repenser votre structure d'application qui ne nécessite pas d'accès synchronisé à ces méthodes. Ou à tout le moins ne se synchronisez pas sur l'objet lui-même.


2 commentaires

Pourquoi le bowvote ?? Si vous regardez les cours du JDK (java.util.date ou java.lang.long), vous constaterez qu'ils ne sont pas synchronisés non plus.


"Si des objets sont autorisés à muter de manière à modifier les résultats de HashCode ou égal à ..." La plupart des objets font - même Java.Util.date (Setttime (long)). Il est toujours nécessaire de garder cela à l'esprit lors de l'utilisation d'une structure de données hachée - ne le change pas après l'avoir déposé. Pourtant, il est permis de le changer.



6
votes

Où est le point dans la synchronisation des égaux () si le résultat n'est pas garanti d'être vrai après la synchronisation après la synchronisation:

synchronized(o1) {
  synchronized(o2) {
    if (o1.equals(o2)) {
      // is o1 still equal to o2?
    }
  }
}


0 commentaires

1
votes

Le seul moyen de savoir si la synchronisation est strictement nécessaire consiste à analyser l'ensemble du programme pour les situations. Il y a deux choses que vous devez rechercher; Situations où un thread change un objet tandis qu'un autre appelle des égaux et des situations où le fil appelant est égal à peut voir une valeur fade de i .

Si vous verrouillez les deux Ce et le objet en même temps que vous risquez effectivement une impasse. Mais je poserais une question à ce que vous devez faire cela. Au lieu de cela, je pense que vous devriez implémenter égaux (objet) comme ceci: xxx

ceci ne garantit pas que les deux objets ont la même valeur de < Code> I en même temps, mais il est peu probable de faire une différence pratique. Après tout, même si vous avez eu cette garantie, vous devez toujours faire face à la question que les deux objets ne peuvent plus être égaux au moment où le est égal à renvoyé. (Ceci est le point de vue @ S!)

En outre, cela n'élimine pas entièrement le risque d'impasse. Considérez le cas où un thread peut appeler égale tout en maintenant une serrure sur l'un des deux objets; par exemple xxx

maintenant si deux threads appellent a.frobbitt (b) et b.frobbitt (a) respectivement, Il y a un risque d'impasse.

(Cependant, vous devez appeler geti () ou déclarer i pour être volatile , sinon le égale () pourrait voir une valeur fade de i s'il a été récemment mis à jour par un fil différent.)

Ceci a été Dit, il y a quelque chose de plus préoccupé par une méthode basée sur une valeur ajoutée sur un objet dont les valeurs des composants peuvent être mutées. Par exemple, cela va briser de nombreux types de collecte. Combinez cela avec un multi-threading et vous allez avoir beaucoup de difficulté à déterminer si votre code est vraiment correct. Je ne peux pas m'empêcher de penser que vous feriez mieux de changer le Equals et HashCode de sorte qu'ils ne dépendent pas d'état pouvant muté après les méthodes qui ont été appelées le premier temps.


4 commentaires

> En outre, cela n'élimine pas entièrement le risque d'impasse. Considérez le cas où un thread peut appeler des égaux tout en maintenant une serrure sur l'un des deux objets. Je ne sais pas comment c'est un problème. Si c'est la serrure de cela, aucun problème comme Geti () ne sera réentrant et n'influencera pas d'autres.Getti (). Même chose si c'est la serrure de l'autre. Juste pour référence sur la façon dont d'autres le font: Vector se synchronise à ce sujet mais pas de l'autre dans des égaux (). Cela peut entraîner une exception simultané une exception si le vecteur est modifié tandis que c'est égal () fonctionne. Atomicinteger ne met pas en œuvre égaux ()


@Philipp - Vous auriez besoin de deux threads en faisant cela avec la même paire d'objets dans des positions opposées pour obtenir une impasse.


Cela ne sera-t-il pas appelé son verrou sur l'autre.Getti () est appelé? Je ne comprends pas comment cela pourrait être une impasse.


this.geti () n'acquit ni ne libère pas le verrou, car frobbitt () l'a déjà acquis avant appeler ceci.geti () . C'est la même situation que dans votre question, mais avec une méthode différente. Deux threads appelant a.froiditt (b) et b.froiditt (a) en même temps acquiert la verrouillage de A resp. B , puis attendez le verrouillage de B resp. A pour appeler autre.geti () , entraînant une impasse.



1
votes

(Je suppose que vous êtes intéressé par l'affaire général ici, et pas seulement dans des entiers emballés.)

Vous ne pouvez pas empêcher deux threads d'appeler définir ... méthodes dans l'ordre arbitraire. Donc, même lorsqu'un thread obtient un (valide) true de l'appelant .fequals ( ... ) , ce résultat pourrait être invalidé immédiatement par un autre Fil que cela appelle définir ... sur l'un des objets. Iow le résultat signifie que les valeurs étaient égales à l'instant de comparaison.

Par conséquent, la synchronisation protégerait contre le cas de la valeur enveloppée étant dans un état incohérent alors que vous essayez de faire le comparateur (par exemple, deux int -sized moitiés d'un long < / code> être mis à jour consécutivement). Vous pouvez éviter une condition de course en copiant chaque valeur (c'est-à-dire synchronisée indépendamment, sans chevauchement), puis comparant les copies.


0 commentaires

-2
votes

lit et écrit à int est déjà atomique, il n'est donc pas nécessaire de synchroniser le getter et le setter (voir http://java.sun.com/docs/books/Tutorial/essential/concurrency/atomic.html ).

De même, vous n'avez pas besoin de synchroniser égale ici. Bien que vous puissiez empêcher un autre thread de changer l'une des valeurs i pendant la comparaison, ce fil bloquait simplement jusqu'à ce que la méthode est terminée et la modifie immédiatement après.


0 commentaires

0
votes

Comme le montre la journée de Jason, l'entier se compare déjà atomique, la synchronisation est donc superflue. Mais si vous construisiez simplement un exemple simplifié et dans la vie réelle, vous envisagez d'un objet plus complexe:

La réponse directe à votre question est, assurez-vous de comparer toujours les éléments dans un ordre cohérent. Peu importe ce que cet ordre est, tant que c'est cohérent. Dans un cas comme celui-ci, System.IDidIfidHashashCode fournirait une commande, comme: xxx

puis déclarer à l'égalshelper privé et laissez le vrai travail de comparaison. < P> (mais WOW, c'est beaucoup de code pour une question aussi triviale.)

Notez que pour que cela fonctionne, toute fonction pouvant modifier l'état de l'objet devrait être déclarée synchronisée. < / p>

Une autre option serait de synchroniser Sync.Class plutôt que sur l'un ou l'autre objet, puis de synchroniser toutes les configurissures sur Sync.Class. Cela ferait tout verrouiller sur un seul mutex et éviterait tout le problème. Bien sûr, en fonction de ce que vous faites cela pourrait entraîner un blocage indésirable de certains threads. Vous devez réfléchir grâce aux implications à la lumière de ce que votre programme est à propos.

S'il s'agit d'un vrai problème dans un projet que vous travaillez, une alternative sérieuse à envisager serait de faire le objet immuable. Pensez à la chaîne et à StringBuilder. Vous pouvez créer un objet SyncBuilder qui vous permet de faire du travail que vous devez créer une de ces choses, puis disposez d'un objet de synchronisation dont l'état est défini par le constructeur et ne peut jamais changer. Créez un constructeur qui prend un syncbuilder et définit son état pour correspondre ou avoir une méthode SyncBuilder.tosync. De toute façon, vous faites tout votre bâtiment dans SyncBuilder, puis transformez-le en une synchronisation et vous êtes maintenant une garantie d'immuabilité afin que vous n'ayez pas à vous gâcher avec la synchronisation du tout.


0 commentaires

3
votes

S'il a été dit assez, les champs que vous utilisez pour HASHCODE (), sont égaux () ou comparète () doivent être immuables, de préférence définitifs. Dans ce cas, vous n'avez pas besoin de les synchroniser.

La seule raison d'implémenter HashCode () est donc l'objet peut être ajouté à une collection de hachage et vous ne pouvez pas valablement modifier le hashcode () d'un objet qui a été ajouté à une telle collection.


2 commentaires

Comme les calculs pour l'arraylist? ;-) Joking de côté, la finale immuable est une bonne chose à rechercher (probablement au-delà d'une bonne chose - tout est SO beaucoup plus facile lorsque vous utilisez des objets immuables), mais ce n'est pas toujours possible avec de grands objets composites .


Comment utilisez-vous HashCode () avec ArrayList? Bien qu'il ne soit peut-être pas possible de rendre les objets immuables, il ne change pas le fait que vous ne pouvez pas modifier le hashcode () d'une clé d'une carte ou d'éléments d'un ensemble et qu'il fonctionne.



0
votes

Ne pas utiliser de synchronisation. Pensez à des haricots non modifiables.


0 commentaires

2
votes

Vous essayez de définir un "égal" basé sur le contenu et "hashcode" sur un objet mutable. Ceci n'est pas seulement impossible: cela n'a pas de sens. Selon

http://java.sun.com.com /javase/6/docs/api/java/lang/Object.html

"Equals" et "HashCode" doivent être cohérents: renvoyez la même valeur pour les invocations successives sur le même objet. Mutabilité par définition empêche cela. Ce n'est pas seulement la théorie: de nombreuses autres classes (par exemple, des collections) dépendent des objets mettant en œuvre la sémantique correcte pour les égaux / HashCode.

Le problème de synchronisation est un hareng rouge ici. Lorsque vous résolvez le problème sous-jacent (mutabilité), vous n'avez pas besoin de synchroniser. Si vous ne résolvez pas le problème de l'entretabilité, aucune synchronisation ne vous aidera.


2 commentaires

Les objets mutables de la JVM effectuent des égaux (Exemple: ArrayList, HASHMAP) Donc, votre affirmation selon laquelle "Contenu" "est égal à" (...) sur un objet mutable (...) n'a pas de sens "n'est pas partagé par Ceux qui écrivent la JVM. Même certaines classes mutables sychronisées implémentent des égaux (exemple vectoriel, qui n'est pas une impasse Safe BTW)!


C'est un bon point. Cependant, je dirais que c'est la responsabilité de l'appelant de rendre ces objets efficacement immuables pour la durée de toute opération qui utilise des égaux / HashCode. Par exemple, la liste d'appel .Contains (x) Lors de la mutation X ou de la mutation des touches de la carte A un comportement non défini.



0
votes

Vous devez vous assurer que les objets ne changent pas entre les appels vers HASHCODE () et sont égaux () (si appelé). Ensuite, vous devez vous assurer que les objets ne changent pas (à l'étendue que les hachices et les égaux sont concernés), tandis que l'objet est assis dans un hachema. Pour changer l'objet, vous devez d'abord le supprimer, puis le changer et le remettre.


0 commentaires

0
votes

Comme d'autres personnes ont mentionné, si les choses changent pendant la vérification des égaux, il existe déjà une possibilité de comportement fou (même avec une synchronisation correcte). Ainsi, tout ce dont vous avez vraiment besoin de vous inquiéter est la visibilité (vous voulez vous assurer de changer de changement qui "se passe avant" votre appel égal à votre équivalence est visible). Par conséquent, vous pouvez simplement faire "instantané" égaux, ce qui sera correct en termes de relation "arrive avant" et ne souffrira pas de problèmes de commande de verrouillage: xxx


0 commentaires