Pourquoi deux AtomicInteger ne sont jamais égaux?
je suis tombé sur la source de AtomicInteger
et réalisé que
new AtomicInteger(0).equals(new AtomicInteger(0))
égale false
.
Pourquoi est-ce? S'agit-il d'un choix de conception" défensif " lié à des questions de concurrence? Dans l'affirmative, qu'est-ce qui pourrait mal tourner s'il était mis en œuvre différemment?
(je me rends compte que je pouvais utiliser get
et ==
à la place.)
9 réponses
C'est en partie parce que un AtomicInteger
n'est pas un objectif général de remplacement pour un Integer
.
java.util.concurrent.atomic
résumé du paquet états:
les classes atomiques ne remplacent pas les
java.lang.Integer
et les classes associées. Ils ne définissent pas les méthodeshashCode
etcompareTo
. (Parce que les variables atomiques sont on s'attend à ce qu'ils soient mutés, ils sont de mauvais choix pour les touches de table de hash.)
hashCode
n'est pas mis en œuvre, et c'est le cas avec equals
. Cela s'explique en partie par un raisonnement beaucoup plus large qui est discuté dans les archives de la liste de diffusion, si AtomicInteger
devrait s'étendre Number
ou pas.
une des raisons pour lesquelles une classe AtomicXXX n'est pas un remplacement direct d'une primitive, et qu'elle n'implémente pas le Comparable
interface, est parce qu'il est inutile de comparer deux instances d'une classe AtomicXXX dans la plupart des scénarios. Si deux threads pouvaient accéder et muter la valeur d'un AtomicInteger
, puis le résultat de la comparaison n'est pas valide avant d'utiliser le résultat, si un thread mute la valeur d'un AtomicInteger
. Le même raisonnement est valable pour l' equals
méthode - le résultat d'un test d'égalité (qui dépend de la valeur de l' AtomicInteger
) n'est valide qu'avant qu'un thread ne mute l'un des AtomicInteger
s en question.
à première vue, cela ressemble à une simple omission, mais cela a peut-être un sens d'utiliser simplement les égaux d'identité fournis par Object.equals
Par exemple:
AtomicInteger a = new AtomicInteger(0)
AtomicInteger b = new AtomicInteger(0)
assert a.equals(b)
semble raisonnable, mais b
n'est pas vraiment a
, il est conçu pour être un support mutable pour une valeur et ne peut donc pas vraiment remplacer a
dans un programme.
aussi:
assert a.equals(b)
assert a.hashCode() == b.hashCode()
devrait fonctionner mais que faire si la valeur de b change dans entre.
si c'est la raison, c'est dommage que cela n'ait pas été documenté dans la source pour AtomicInteger
.
à part: une belle caractéristique aurait pu être d'autoriser AtomicInteger
pour être égal à un entier.
AtomicInteger a = new AtomicInteger(25);
if( a.equals(25) ){
// woot
}
trouble cela signifierait que pour être réflexif dans ce cas entier devrait accepter AtomicInteger
c'est égal.
je dirais que, parce que le point de AtomicInteger
est que les opérations peuvent être effectuées atomiquement, il serait difficile de s'assurer que les deux valeurs sont comparées atomiquement, et parce que les indicateurs Atomic sont généralement des compteurs, vous obtiendriez un comportement étrange.
Donc, sans s'assurer que le equals
la méthode est synchronisée vous ne seriez pas sûr que la valeur de l'entier atomique n'a pas changé à la fois equals
retourne. Cependant, comme tout le point d'un entier atomique n'est pas pour utiliser la synchronisation, vous finirais avec peu d'avantages.
je soupçonne que la comparaison des valeurs est un non-go puisqu'il n'y a aucun moyen de le faire atomiquement d'une manière portable (sans serrures, c'est-à-dire).
et s'il n'y a pas d'atomicité alors les variables pourraient comparer égale même ils n'ont jamais contenu la même valeur en même temps (par exemple si a
modifié 0
1
exactement au même moment que b
modifié 1
0
).
AtomicInteger hérite de L'objet et non de L'entier, et il utilise la vérification de l'égalité de référence standard.
Si vous google, vous trouverez cette discussion de ce cas précis.
Imaginez si equals
a été remplacé et vous mettre dans un HashMap
et puis vous changez la valeur. Les mauvaises choses qui se passera:)
equals
est correctement implémenté: un AtomicInteger
instance ne peut être égale qu'à elle-même, car seule une même instance stockera la même séquence de valeurs dans le temps.
rappelons que Atomic*
les classes agissent comme des types de référence (tout comme java.lang.ref.*
), destiné à envelopper une valeur réelle, "utile". Contrairement à ce qui se passe dans les langues fonctionnelles (voir par exemple Clojure Atom
ou Haskell IORef
), la distinction entre les références et les valeurs est plutôt floue en Java (blâme mutabilité), mais elle est toujours là.
considérer la valeur enveloppée actuelle d'une classe atomique comme critère d'égalité est clairement une idée fausse, car cela impliquerait que new AtomicInteger(1).equals(1)
.
une limite avec Java est qu'il n'y a aucun moyen de distinguer une instance de classe mutable qui peut et sera mutée, d'une instance de classe mutable qui ne sera jamais exposée à quoi que ce soit qui pourrait la muter(*). Les références à des choses du premier type ne doivent être considérées comme égales que si elles se rapportent au même objet, tandis que les références à des choses du second type doivent souvent être considérées comme égales si elles se rapportent à des objets ayant un État équivalent. Parce que Java n'en autorise qu'un seul Outrepasser le virtuel equals(object)
méthode, les concepteurs de classes mutables doivent deviner si suffisamment d'instances répondront à ce dernier modèle (c.-à-d. qu'elles seront tenues de telle sorte qu'elles ne seront jamais mutées) pour justifier d'avoir equals()
et hashCode()
se comporter d'une manière appropriée pour un tel usage.
Dans le cas de quelque chose comme Date
, il y a beaucoup de classes qui encapsulent une référence à un Date
qui ne sera jamais modifié, et qui veulent avoir leur propre relation d'équivalence incorporer la valeur-équivalence de l'encapsulé Date
. En tant que tel, cela a du sens pour Date
remplacer equals
et hashCode
pour tester l'équivalence des valeurs. D'un autre côté, en tenant une référence à un AtomicInteger
qui ne va jamais être modifié serait stupide, puisque l'ensemble but de ce type se concentre autour de la mutabilité. AtomicInteger
cas qui ne va jamais être muté peut, à toutes fins pratiques, tout simplement être un Integer
.
(*) toute exigence voulant qu'une instance particulière ne mute jamais ne lie qu'aussi longtemps que (1) l'information sur sa valeur de hachage d'identité existe quelque part, ou (2) plus d'une référence à l'objet existe quelque part dans l'univers. Si aucune des deux conditions ne s'applique à l'instance mentionnée par Foo
, en remplaçant Foo
avec une référence à un clone de Foo
aurait aucun effet observable. Par conséquent, on pourrait muter l'instance sans violant une exigence selon laquelle il "ne mute jamais" en prétendant remplacer Foo
avec un clone et mutant le"clone".
equals
n'est pas seulement utilisé pour l'égalité mais aussi pour respecter son contrat avec hashCode
, i.e. dans les collections de hash. La seule approche sûre pour les collections de hash est que les objets mutables ne dépendent pas de leur contenu. c'est-à-dire que pour les clés mutables un HashMap est la même chose qu'utiliser un IdentityMap. De cette façon, le hashCode et le fait que deux objets soient égaux ne changent pas lorsque le contenu des clés change.
new StringBuilder().equals(new StringBuilder())
est aussi faux.
Pour comparer le contenu de deux AtomicInteger, vous avez besoin d' ai.get() == ai2.get()
ou ai.intValue() == ai2.intValue()
disons que vous aviez une clé mutable où le hashCode et les égaux changeaient selon le contenu.
static class BadKey {
int num;
@Override
public int hashCode() {
return num;
}
@Override
public boolean equals(Object obj) {
return obj instanceof BadKey && num == ((BadKey) obj).num;
}
@Override
public String toString() {
return "Bad Key "+num;
}
}
public static void main(String... args) {
Map<BadKey, Integer> map = new LinkedHashMap<BadKey, Integer>();
for(int i=0;i<10;i++) {
BadKey bk1 = new BadKey();
bk1.num = i;
map.put(bk1, i);
bk1.num = 0;
}
System.out.println(map);
}
imprime
{Bad Key 0=0, Bad Key 0=1, Bad Key 0=2, Bad Key 0=3, Bad Key 0=4, Bad Key 0=5, Bad Key 0=6, Bad Key 0=7, Bad Key 0=8, Bad Key 0=9}
comme vous pouvez le voir nous avons maintenant 10 Clés, toutes égales et avec le même hashCode!