为什么两个原子整数永远不相等?

发布于 2024-12-06 20:33:37 字数 261 浏览 0 评论 0原文

我偶然发现了 AtomicInteger 的源代码,并意识到其

new AtomicInteger(0).equals(new AtomicInteger(0))

计算结果为 false。

这是为什么呢?这是与并发问题相关的某种“防御性”设计选择吗?如果是这样,如果以不同的方式实施会出现什么问题?

(我确实意识到我可以使用 get== 来代替。)

I stumbled across the source of AtomicInteger and realized that

new AtomicInteger(0).equals(new AtomicInteger(0))

evaluates to false.

Why is this? Is it some "defensive" design choice related to concurrency issues? If so, what could go wrong if it was implemented differently?

(I do realize I could use get and == instead.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

孤星 2024-12-13 20:33:37

部分原因是 AtomicInteger 不是 Integer 的通用替代品。

java.util.concurrent.atomic 包摘要 指出:

原子类不是通用目的的替代品
java.lang.Integer 及相关类。他们没有定义方法
例如 hashCodecompareTo。 (因为原子变量是
预计会发生变异,因此它们对于哈希表键来说是糟糕的选择。)

hashCode 未实现,equals 的情况也是如此。这部分是由于中讨论的一个更大的理由。邮件列表存档,关于 AtomicInteger 是否应该扩展 Number

AtomicXXX 类不是原语的直接替代品,并且它没有实现 Comparable 接口的原因之一是,比较 AtomicXXX 类的两个实例是没有意义的。大多数场景。如果两个线程可以访问并改变 AtomicInteger 的值,则 如果线程改变 AtomicInteger 的值,则在使用结果之前比较结果无效。同样的原理也适用于 equals 方法 - 相等测试的结果(取决于 AtomicInteger 的值)仅在线程改变其中之一之前才有效。有问题的AtomicInteger

This is partly because an AtomicInteger is not a general purpose replacement for an Integer.

The java.util.concurrent.atomic package summary states:

Atomic classes are not general purpose replacements for
java.lang.Integer and related classes. They do not define methods
such as hashCode and compareTo. (Because atomic variables are
expected to be mutated, they are poor choices for hash table keys.)

hashCode is not implemented, and so is the case with equals. This is in part due to a far larger rationale that is discussed in the mailing list archives, on whether AtomicInteger should extend Number or not.

One of the reasons why an AtomicXXX class is not a drop-in replacement for a primitive, and that it does not implement the Comparable interface, is because it is pointless to compare two instances of an AtomicXXX class in most scenarios. If two threads could access and mutate the value of an AtomicInteger, then the comparison result is invalid before you use the result, if a thread mutates the value of an AtomicInteger. The same rationale holds good for the equals method - the result for an equality test (that depends on the value of the AtomicInteger) is only valid before a thread mutates one of the AtomicIntegers in question.

长梦不多时 2024-12-13 20:33:37

从表面上看,这似乎是一个简单的遗漏,但实际上仅使用 Object.equals 提供的身份等于确实有意义,

例如:

AtomicInteger a = new AtomicInteger(0)
AtomicInteger b = new AtomicInteger(0)

assert a.equals(b)

看起来合理,但是 b 并不是真正的 a,它被设计为值的可变持有者,因此不能真正替换程序中的 a

另外:

assert a.equals(b)
assert a.hashCode() == b.hashCode()

应该可以,但是如果 b 的值在两者之间发生变化怎么办?

如果这就是原因,那么遗憾的是它没有记录在 AtomicInteger 的源代码中。

顺便说一句:一个不错的功能可能是允许 AtomicInteger 等于 Integer。

AtomicInteger a = new AtomicInteger(25);

if( a.equals(25) ){
    // woot
}

麻烦的是,这意味着为了在这种情况下具有自反性,Integer 也必须在它的 equals 中接受 AtomicInteger

On the face of it, it seems like a simple omission but it maybe it does make some sense to actually just use the idenity equals provided by Object.equals

For instance:

AtomicInteger a = new AtomicInteger(0)
AtomicInteger b = new AtomicInteger(0)

assert a.equals(b)

seems reasonable, but b isn't really a, it is designed to be a mutable holder for a value and therefore can't really replace a in a program.

also:

assert a.equals(b)
assert a.hashCode() == b.hashCode()

should work but what if b's value changes in between.

If this is the reason it's a shame it wasn't documented in the source for AtomicInteger.

As an aside: A nice feature might also have been to allow AtomicInteger to be equal to an Integer.

AtomicInteger a = new AtomicInteger(25);

if( a.equals(25) ){
    // woot
}

trouble it would mean that in order to be reflexive in this case Integer would have to accept AtomicInteger in it's equals too.

淡忘如思 2024-12-13 20:33:37

我认为,因为 AtomicInteger 的要点是操作可以原子地完成,所以很难确保两个值以原子方式进行比较,并且因为 AtomicIntegers 通常是计数器,所以你会出现一些奇怪的行为。

因此,如果不确保 equals 方法同步,您就无法确定在 equals 返回时原子整数的值没有更改。然而,由于原子整数的全部意义不是使用同步,所以你最终不会得到什么好处。

I would argue that because the point of an AtomicInteger is that operations can be done atomically, it would be be hard to ensure that the two values are compared atomically, and because AtomicIntegers are generally counters, you'd get some odd behaviour.

So without ensuring that the equals method is synchronised you wouldn't be sure that the value of the atomic integer hasn't changed by the time equals returns. However, as the whole point of an atomic integer is not to use synchronisation, you'd end up with little benefit.

音栖息无 2024-12-13 20:33:37

我怀疑比较这些值是不行的,因为没有办法以可移植的方式原子地完成它(即没有锁)。

如果没有原子性,那么即使变量从未同时包含相同的值,变量也可以比较相等(例如,如果 a0 更改为 1 与 b1 更改为 0 的同一时间)。

I suspect that comparing the values is a no-go since there's no way to do it atomically in a portable fashion (without locks, that is).

And if there's no atomicity then the variables could compare equal even they never contained the same value at the same time (e.g. if a changed from 0 to 1 at exactly the same time as b changed from 1 to 0).

冰雪之触 2024-12-13 20:33:37

AtomicInteger 继承自 Object 并不是 Integer,它使用标准引用相等性检查。

如果你用谷歌搜索,你会发现这个具体案例的讨论< /a>.

AtomicInteger inherits from Object and not Integer, and it uses standard reference equality check.

If you google you will find this discussion of this exact case.

一口甜 2024-12-13 20:33:37

想象一下,如果 equals 被覆盖,并将其放入 HashMap 中,然后更改值。不好的事情会发生:)

Imagine if equals was overriden and you put it in a HashMap and then you change the value. Bad things will happen:)

有深☉意 2024-12-13 20:33:37

equals 不仅用于相等,而且还满足其与 hashCode 的约定,即在哈希集合中。哈希集合的唯一安全方法是可变对象不依赖于其内容。即对于可变键,HashMap 与使用 IdentityMap 相同。这样,当键内容改变时,hashCode 和两个对象是否相等不会改变。

所以 new StringBuilder().equals(new StringBuilder()) 也是 false。

要比较两个 AtomicInteger 的内容,需要 ai.get() == ai2.get()ai.intValue() == ai2.intValue()

让假设您有一个可变键,其中 hashCode 和 equals 根据内容而变化。

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);
}

正如

{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}

你所看到的,我们现在有 10 个键,它们都相等并且具有相同的 hashCode!

equals is not only used for equality but also to meet its contract with hashCode, i.e. in hash collections. The only safe approach for hash collections is for mutable object not to be dependant on their contents. i.e. for mutable keys a HashMap is the same as using an IdentityMap. This way the hashCode and whether two objects are equal does not change when the keys content changes.

So new StringBuilder().equals(new StringBuilder()) is also false.

To compare the contents of two AtomicInteger, you need ai.get() == ai2.get() or ai.intValue() == ai2.intValue()

Lets say that you had a mutable key where the hashCode and equals changed based on the contents.

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);
}

prints

{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}

As you can see we now have 10 keys, all equal and with the same hashCode!

独闯女儿国 2024-12-13 20:33:37

equals 已正确实现:AtomicInteger 实例只能等于其自身,因为随着时间的推移,只有同一个实例才能证明存储相同的值序列。

请记住,Atomic* 类充当引用类型(就像 java.lang.ref.*),旨在包装实际的“有用”值。与函数式语言中的情况不同(参见 Clojure 的 Atom 或 Haskell 的 IORef ),Java 中引用和值之间的区别相当模糊(归咎于可变性),但它仍然在那里。

将 Atomic 类的当前包装值视为相等的标准显然是一种误解,因为这意味着 new AtomicInteger(1).equals(1)

equals is correctly implemented: an AtomicInteger instance can only equal itself, as only that very same instance will provably store the same sequence of values over time.

Please recall that Atomic* classes act as reference types (just like java.lang.ref.*), meant to wrap an actual, "useful" value. Unlike it is the case in functional languages (see e.g. Clojure's Atom or Haskell's IORef), the distinction between references and values is rather blurry in Java (blame mutability), but it is still there.

Considering the current wrapped value of an Atomic class as the criterion for equality is quite clearly a misconception, as it would imply that new AtomicInteger(1).equals(1).

俯瞰星空 2024-12-13 20:33:37

Java 的一个限制是,无法区分可以且将会发生变异的可变类实例与永远不会暴露于可能发生变异的任何事物的可变类实例 (*)。对前一种类型的事物的引用只有在引用同一对象时才应被视为相等,而对后一种类型的事物的引用如果引用具有相同状态的对象,则通常应被视为相等。因为 Java 只允许对虚拟 equals(object) 方法进行一次重写,所以可变类的设计者必须猜测是否有足够的实例满足后一种模式(即以永远不会出现的方式保存)。被变异)来证明 equals()hashCode() 以适合这种用法的方式运行。

对于像 Date 这样的情况,有很多类封装了对永远不会被修改的 Date 的引用,并希望拥有自己的等价关系并包含封装的 Date 的值等价性。因此,Date 重写 equalshashCode 来测试值的等价性是有意义的。另一方面,持有对永远不会被修改的 AtomicInteger 的引用是愚蠢的,因为该类型的整个目的以可变性为中心。出于所有实际目的,永远不会改变的 AtomicInteger 实例可能只是一个 Integer

(*) 只要 (1) 某处存在有关其身份哈希值的信息,或者 (2) 宇宙中某处存在对该对象的多个引用,任何特定实例永不变异的要求才具有约束力。如果这两个条件都不适用于 Foo 引用的实例,则将 Foo 替换为对 Foo 克隆的引用将不会产生明显的效果。因此,通过假装用克隆替换 Foo 并改变“克隆”,就可以在不违反“从不改变”的要求的情况下改变实例。

One limitation with Java is that there is no means of distinguishing a mutable-class instance which can and will be mutated, from a mutable-class instance which will never be exposed to anything that might mutate it(*). References to things of the former type should only be considered equal if they refer to the same object, while references to things of the latter type should often be considered equal if the refer to objects with equivalent state. Because Java only allows one override of the virtual equals(object) method, designers of mutable classes have to guess whether enough instances will meet the latter pattern (i.e. be held in such a way that they'll never be mutated) to justify having equals() and hashCode() behave in a fashion suitable for such usage.

In the case of something like Date, there are a lot of classes which encapsulate a reference to a Date that is never going to be modified, and which want to have their own equivalence relation incorporate the value-equivalence of the encapsulated Date. As such, it makes sense for Date to override equals and hashCode to test value equivalence. On the other hand, holding a reference to an AtomicInteger that is never going to be modified would be silly, since the whole purpose of that type centers around mutability. An AtomicInteger instance which is never going to be mutated may, for all practical purposes, simply be an Integer.

(*) Any requirement that a particular instance never mutate is only binding as long as either (1) information about its identity hash value exists somewhere, or (2) more than one reference to the object exists somewhere in the universe. If neither condition applies to the instance referred to by Foo, replacing Foo with a reference to a clone of Foo would have no observable effect. Consequently, one would be able to mutate the instance without violating a requirement that it "never mutate" by pretending to replace Foo with a clone and mutating the "clone".

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文