我偶然发现了AtomicInteger
的来源并意识到了
new AtomicInteger(0).equals(new AtomicInteger(0))
等于false
。
这是为什么?这是与并发问题相关的一些“防御性”设计选择吗?如果是这样,如果以不同的方式实施会出现什么问题?
(我确实知道我可以改用get
和==
。)
答案 0 :(得分:27)
部分原因是AtomicInteger
不是Integer
的通用替代品。
java.util.concurrent.atomic
package summary州:
原子类不是通用的替代品
java.lang.Integer
和相关课程。他们没有定义方法 例如hashCode
和compareTo
。 (因为原子变量是 预计会发生变异,它们对哈希表键的选择很差。)
hashCode
未实现,equals
的情况也是如此。这部分是由于AtomicInteger
是否应该Number
扩展Comparable
的更大理由discussed in the mailing list archives。
AtomicXXX类不是基元的替代品,并且它没有实现AtomicInteger
接口的原因之一是因为比较AtomicXXX类的两个实例是没有意义的。大多数情况。如果两个线程可以访问并改变equals
的值,那么the comparison result is invalid before you use the result, if a thread mutates the value of an AtomicInteger
。相同的基本原理适用于AtomicInteger
方法 - 相等性测试的结果(取决于AtomicInteger
的值)仅在线程改变其中一个{{1}}之前有效问题。
答案 1 :(得分:10)
从表面上看,这似乎是一个简单的遗漏,但实际上只使用Object.equals
例如:
AtomicInteger a = new AtomicInteger(0)
AtomicInteger b = new AtomicInteger(0)
assert a.equals(b)
似乎合理,但b
并非a
,它被设计为值的可变持有者,因此无法真正替换程序中的a
。< / p>
也:
assert a.equals(b)
assert a.hashCode() == b.hashCode()
应该可以工作,但如果b的值在两者之间发生变化会怎样。
如果这是一个耻辱的原因,那么AtomicInteger
的来源就没有记录。
顺便说一下:一个很好的功能也可能是允许AtomicInteger
等于整数。
AtomicInteger a = new AtomicInteger(25);
if( a.equals(25) ){
// woot
}
麻烦,这意味着为了在这种情况下反思,整数也必须接受AtomicInteger
等于它。
答案 2 :(得分:3)
我认为,因为AtomicInteger
的意思是操作可以原子方式完成,所以很难确保两个值在原子上进行比较,并且因为AtomicIntegers通常是计数器,所以你需要得到一些奇怪的行为。
因此,如果不确保同步equals
方法,则无法确定原子整数的值在equals
返回的时间内没有改变。但是,由于原子整数的整个点不是使用同步,因此最终收效甚微。
答案 3 :(得分:3)
我怀疑比较这些值是不行的,因为没有办法以便携的方式原子地做到这一点(没有锁,就是这样)。
如果没有原子性,则变量可以比较相等,即使它们从未同时包含相同的值(例如,如果a
从0
更改为1
则完全相同b
从1
更改为0
)的时间。
答案 4 :(得分:1)
AtomicInteger继承自Object而不是Integer,它使用标准引用相等性检查。
如果你谷歌,你会发现this discussion of this exact case。
答案 5 :(得分:1)
想象一下,如果equals
被覆盖并且您将其放在HashMap
中,然后您更改了该值。不好的事情会发生:)
答案 6 :(得分:1)
equals
已正确实现:AtomicInteger
实例只能与自身相等,因为只有那个相同的实例才会在一段时间内证明存储相同的值序列。
请注意Atomic*
类充当引用类型(就像java.lang.ref.*
),意味着包含一个实际的“有用”值。与功能语言中的情况不同(参见例如Clojure的Atom
或Haskell的IORef
),引用和值之间的区别在Java中非常模糊(责备可变性),但它仍然存在。
考虑到Atomic类的当前包装值作为相等的标准显然是一种误解,因为它意味着new AtomicInteger(1).equals(1)
。
答案 7 :(得分:1)
Java的一个限制是,没有办法区分一个可变的实例,这个实例可以并且将会被突变,这个实例永远不会暴露给任何可能使它变异的可变类实例(*)。如果引用具有相同状态的对象,则引用前一类型的引用只应被视为相等,如果它们引用相同的对象,则对后一类型的引用通常应被视为相等。因为Java只允许对虚拟equals(object)
方法进行一次覆盖,所以可变类的设计者必须猜测是否有足够的实例满足后一种模式(即以永远不会变异的方式保存)来证明equals()
和hashCode()
的行为方式适合此类使用。
在类似Date
的情况下,有很多类封装了对Date
的引用,该引用永远不会被修改,并且需要使自己的等价关系包含封装的Date
的值等价。因此,Date
覆盖equals
和hashCode
以测试值等效是有意义的。另一方面,持有对永远不会被修改的AtomicInteger
的引用将是愚蠢的,因为该类型的整个目的以可变性为中心。对于所有实际目的,永远不会发生变异的AtomicInteger
实例可能只是Integer
。
(*)只要(1)有关其身份哈希值的信息存在于某处,或(2)对象的多个引用存在于Universe中的任何位置,任何特定实例永远不会变异的要求都只是绑定。如果这两个条件都不适用于Foo
引用的实例,则将Foo
替换为对Foo
的克隆的引用将没有可观察到的影响。因此,人们可以通过假装用克隆替换Foo
并改变“克隆”来违反要求“从不变异”的变更实例。
答案 8 :(得分:0)
equals
不仅用于平等,还用于与hashCode
签订合同,即在哈希集合中。散列集合的唯一安全方法是可变对象不依赖于它们的内容。即对于可变键,HashMap与使用IdentityMap相同。这样,当密钥内容发生变化时,hashCode以及两个对象是否相等不会改变。
所以new StringBuilder().equals(new StringBuilder())
也是假的。
要比较两个AtomicInteger的内容,您需要ai.get() == ai2.get()
或ai.intValue() == ai2.intValue()
假设您有一个mutable键,其中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!