为什么两个原子整数永远不相等?
我偶然发现了 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
部分原因是
AtomicInteger
不是Integer
的通用替代品。java.util.concurrent.atomic
包摘要 指出: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 anInteger
.The
java.util.concurrent.atomic
package summary states:hashCode
is not implemented, and so is the case withequals
. This is in part due to a far larger rationale that is discussed in the mailing list archives, on whetherAtomicInteger
should extendNumber
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 anAtomicInteger
, then the comparison result is invalid before you use the result, if a thread mutates the value of anAtomicInteger
. The same rationale holds good for theequals
method - the result for an equality test (that depends on the value of theAtomicInteger
) is only valid before a thread mutates one of theAtomicInteger
s in question.从表面上看,这似乎是一个简单的遗漏,但实际上仅使用
Object.equals
提供的身份等于确实有意义,例如:
看起来合理,但是
b
并不是真正的a
,它被设计为值的可变持有者,因此不能真正替换程序中的a
。另外:
应该可以,但是如果 b 的值在两者之间发生变化怎么办?
如果这就是原因,那么遗憾的是它没有记录在
AtomicInteger
的源代码中。顺便说一句:一个不错的功能可能是允许 AtomicInteger 等于 Integer。
麻烦的是,这意味着为了在这种情况下具有自反性,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:
seems reasonable, but
b
isn't reallya
, it is designed to be a mutable holder for a value and therefore can't really replacea
in a program.also:
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.trouble it would mean that in order to be reflexive in this case Integer would have to accept
AtomicInteger
in it's equals too.我认为,因为
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 timeequals
returns. However, as the whole point of an atomic integer is not to use synchronisation, you'd end up with little benefit.我怀疑比较这些值是不行的,因为没有办法以可移植的方式原子地完成它(即没有锁)。
如果没有原子性,那么即使变量从未同时包含相同的值,变量也可以比较相等(例如,如果
a
从0
更改为1 与
b
从1
更改为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 from0
to1
at exactly the same time asb
changed from1
to0
).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.
想象一下,如果
equals
被覆盖,并将其放入HashMap
中,然后更改值。不好的事情会发生:)Imagine if
equals
was overriden and you put it in aHashMap
and then you change the value. Bad things will happen:)equals
不仅用于相等,而且还满足其与hashCode
的约定,即在哈希集合中。哈希集合的唯一安全方法是可变对象不依赖于其内容。即对于可变键,HashMap 与使用 IdentityMap 相同。这样,当键内容改变时,hashCode 和两个对象是否相等不会改变。所以
new StringBuilder().equals(new StringBuilder())
也是 false。要比较两个 AtomicInteger 的内容,需要
ai.get() == ai2.get()
或ai.intValue() == ai2.intValue()
让假设您有一个可变键,其中 hashCode 和 equals 根据内容而变化。
正如
你所看到的,我们现在有 10 个键,它们都相等并且具有相同的 hashCode!
equals
is not only used for equality but also to meet its contract withhashCode
, 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()
orai.intValue() == ai2.intValue()
Lets say that you had a mutable key where the hashCode and equals changed based on the contents.
prints
As you can see we now have 10 keys, all equal and with the same hashCode!
equals
已正确实现:AtomicInteger
实例只能等于其自身,因为随着时间的推移,只有同一个实例才能证明存储相同的值序列。请记住,
Atomic*
类充当引用类型(就像java.lang.ref.*
),旨在包装实际的“有用”值。与函数式语言中的情况不同(参见 Clojure 的 Atom 或 Haskell 的 IORef ),Java 中引用和值之间的区别相当模糊(归咎于可变性),但它仍然在那里。将 Atomic 类的当前包装值视为相等的标准显然是一种误解,因为这意味着
new AtomicInteger(1).equals(1)
。equals
is correctly implemented: anAtomicInteger
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 likejava.lang.ref.*
), meant to wrap an actual, "useful" value. Unlike it is the case in functional languages (see e.g. Clojure'sAtom
or Haskell'sIORef
), 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)
.Java 的一个限制是,无法区分可以且将会发生变异的可变类实例与永远不会暴露于可能发生变异的任何事物的可变类实例 (*)。对前一种类型的事物的引用只有在引用同一对象时才应被视为相等,而对后一种类型的事物的引用如果引用具有相同状态的对象,则通常应被视为相等。因为 Java 只允许对虚拟
equals(object)
方法进行一次重写,所以可变类的设计者必须猜测是否有足够的实例满足后一种模式(即以永远不会出现的方式保存)。被变异)来证明equals()
和hashCode()
以适合这种用法的方式运行。对于像
Date
这样的情况,有很多类封装了对永远不会被修改的Date
的引用,并希望拥有自己的等价关系并包含封装的Date
的值等价性。因此,Date
重写equals
和hashCode
来测试值的等价性是有意义的。另一方面,持有对永远不会被修改的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 havingequals()
andhashCode()
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 aDate
that is never going to be modified, and which want to have their own equivalence relation incorporate the value-equivalence of the encapsulatedDate
. As such, it makes sense forDate
to overrideequals
andhashCode
to test value equivalence. On the other hand, holding a reference to anAtomicInteger
that is never going to be modified would be silly, since the whole purpose of that type centers around mutability. AnAtomicInteger
instance which is never going to be mutated may, for all practical purposes, simply be anInteger
.(*) 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
, replacingFoo
with a reference to a clone ofFoo
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 replaceFoo
with a clone and mutating the "clone".