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.)

29
demandé sur aioobe 2011-09-27 14:15:42

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éthodes hashCode et compareTo. (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.

25
répondu Vineet Reynolds 2013-07-25 11:34:16

à 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.

10
répondu Gareth Davis 2011-09-27 10:27:28

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.

3
répondu beny23 2011-09-27 10:35:05

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é 01 exactement au même moment que b modifié 10).

3
répondu NPE 2011-09-27 10:35:39

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.

1
répondu jornb87 2011-09-27 10:28:26

Imaginez si equals a été remplacé et vous mettre dans un HashMap et puis vous changez la valeur. Les mauvaises choses qui se passera:)

1
répondu Petar Minchev 2011-09-27 10:30:27

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).

1
répondu vemv 2013-01-04 02:29:24

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 Datequi 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".

1
répondu supercat 2013-07-25 23:28:07

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!

0
répondu Peter Lawrey 2011-09-27 11:29:12