为什么两个AtomicIntegers永远不相等?

时间:2011-09-27 10:15:43

标签: java concurrency equals

我偶然发现了AtomicInteger的来源并意识到了

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

等于false

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

(我确实知道我可以改用get==。)

9 个答案:

答案 0 :(得分:27)

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

java.util.concurrent.atomic package summary州:

  

原子类不是通用的替代品   java.lang.Integer和相关课程。他们没有定义方法   例如hashCodecompareTo。 (因为原子变量是   预计会发生变异,它们对哈希表键的选择很差。)

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)

我怀疑比较这些值是不行的,因为没有办法以便携的方式原子地做到这一点(没有锁,就是这样)。

如果没有原子性,则变量可以比较相等,即使它们从未同时包含相同的值(例如,如果a0更改为1则完全相同b1更改为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覆盖equalshashCode以测试值等效是有意义的。另一方面,持有对永远不会被修改的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!