这一天让我心烦意乱,所以我正在寻找一个具体的答案。
(拜托,请耐心等待,我不打算制作出漂亮的代码。相反,我正在寻找有嗅觉代码的问题)
想象一下,我们有一个来自班级Foo
public class Foo {
public int attribute = 0;
// hashCode implemented :P
@Override
public boolean equals(Object o) {
if (o instanceof Foo) {
Foo that = (Foo) o;
return this.attribute == that.attribute;
}
return false;
}
}
我们在Foo上有一些工人
public class DoomBringer implements Runnable {
private final Foo foo;
public DoomBringer(Foo foo) {
this.foo = foo;
}
@Override
public void run() {
this.foo.attribute++;
}
}
另一个只将#equals
的结果作为参数传递给它的构造函数。
public class SelfEqualityTestPrinter implements Runnable {
private final Foo foo;
public SelfEqualityTestPrinter(Foo foo) {
this.foo = foo;
}
@Override
public void run() {
System.out.println(foo.equals(foo));
}
}
如果我们有并发线程修改相同的false
实例,是否有可能在某一天打印Foo
我的猜测是可能的。除非我们这样做,否则我认为equals不会同步。避免这种情况的一种方法是在this == o
方法上测试Object#equals(Object o)
,但是通过比较应该相同的不同实例可能会出现同样的问题。
答案 0 :(得分:4)
是的,在存在并发修改的情况下,foo.equals(foo)
完全有可能返回false
。解决这个问题的一种方法是考虑谁阅读和修改内容,并相应地进行同步。
答案 1 :(得分:2)
是的,它可能。任何可以访问这些变量的线程都可以随时更改任何未同步的变量。
如果foo.attribute
的访问权限已同步,并且引用.equals
时attribute
已同步,则情况并非如此。
更令人惊讶的是,如果一个线程更改attribute
,另一个线程可以在之后读取它并获得其旧值!最后一篇文章可以通过使用volatile关键字来修复。这是一个很好的explanation of the volatile keyword。
正如Brian和yshavit所说,不宣布变量波动的问题包括竞争条件。例如,在这里,调用attribute++
的2个线程可能导致attribute
仅增加一次。要递增的第二个线程可以使用存储在其缓存内存中的attribute
的未递增值,而不是第一个线程写入的递增值。