可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I stumbled across the source of AtomicInteger
and realized that
new AtomicInteger(0).equals(new AtomicInteger(0))
equals 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.)
回答1:
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 AtomicInteger
s in question.
回答2:
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.
回答3:
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.
回答4:
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
).
回答5:
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.
回答6:
Imagine if equals
was overriden and you put it in a HashMap
and then you change the value. Bad things will happen:)
回答7:
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)
.
回答8:
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".
回答9:
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!