1

这几天一直困扰着我,所以我正在寻找一个具体的答案。

(请多多包涵,我并不是要编写漂亮的代码。相反,我正在寻找有气味的代码的问题)

想象一下,我们有一个来自类的有状态对象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)方法上进行测试,但是比较应该相等的不同实例可能会出现同样的问题。

4

2 回答 2

4

是的,在存在并发修改的情况下,完全有可能foo.equals(foo)返回false. 解决这个问题的一种方法是考虑谁读取和修改了什么,并相应地进行同步。

于 2013-08-09T19:15:45.853 回答
2

是的,有可能。任何未同步的变量都可以随时由任何线程更改,这些线程可以访问这些变量。

如果访问 tofoo.attribute是同步的,并且.equals在引用 时是同步的,attribute则情况并非如此。

更令人惊讶的是,如果一个线程发生变化attribute,另一个线程可以在之后读取它并获得它的旧值!最后一块可以通过使用 volatile 关键字来修复。这是 volatile 关键字的一个很好的解释

正如 Brian 和 yshavit 所说,不声明变量 volatile 的问题包括竞争条件。例如,这里有 2 个线程调用attribute++可能attribute只增加一次。要递增的第二个线程可以使用attribute存储在他的缓存内存中的未递增值,而不是第一个线程写入的递增值。

于 2013-08-09T19:16:23.003 回答