Java 对象锁等于还是同步等于? (对哈希码也有效)

Java object locks equals or synchronized equals? (valid for hashcode as well)

在调用 equals 方法期间推荐的同步方式是什么,是覆盖 equals 并添加关键字 synchronized 还是在调用 equals 时获取两个对象的锁更好?

例如,我有:

public class EqualsTesterHelper {

    public int test = 0;

    @Override
    public boolean equals(Object obj) {

        if (!(obj instanceof EqualsTesterHelper)){
            return false;
        }
        EqualsTesterHelper a = (EqualsTesterHelper) obj;
        return (a.test == this.test);
    }   
}

现在如果我使用多线程测试 equals 方法,它显然会失败,例如:

public class EqualsTesterMain{
    private static EqualsTesterHelper helper1 = new EqualsTesterHelper();
    private static EqualsTesterHelper helper2 = new EqualsTesterHelper();
    private static Random rand = new Random();

    public static void main(String[] args) {
        helper1.test = 1;
        helper2.test = 1;
        System.out.println(helper1.equals(helper2));
        ExecutorService executor = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++){
            executor.execute(new RunnerTest());
        }

    }

    private static class RunnerTest implements Runnable{

        public void run() {
            while (true){
                modifyHelper(helper1, rand.nextInt(10)); 
                helper1.equals(helper2);
                modifyHelper(helper2, rand.nextInt(10));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        private void modifyHelper(EqualsTesterHelper helper, int newValue){
            helper.test = newValue;
        }   
    }
}

由于数据竞争,我显然得到了不一致的结果。

所以我将 EqualsTesterHelper equals 方法修改为 :

会更好吗
@Override
public  synchronized boolean equals(Object obj) {
    synchronized(obj){
        if (!(obj instanceof EqualsTesterHelper)){
            return false;
        }
        EqualsTesterHelper a = (EqualsTesterHelper) obj;
        System.out.println("In equals, a.test: " + a.test );
        System.out.println("In equals, this.test: " + this.test );
        System.out.println("In equals, equals: " + (this.test  == a.test));
        return (a.test == this.test);
    }

}

还是在我的 Runnable 中对两个对象都添加锁定更好:

 public void run() {
        while (true){
            modifyHelper(helper1, rand.nextInt(10));
            synchronized(helper1){
                synchronized(helper2){
                    helper1.equals(helper2);
                }
            }
            modifyHelper(helper2, rand.nextInt(10));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

我看过 this thread,但似乎唯一的答案是您应该确保在计算 equals(或哈希码)期间不修改对象,这并没有暗示哪个解决方案会更好.

编辑:实际上我意识到我的 "synchronized" equals 有缺陷,因为我仍然需要同步方法中传递的对象。

同步没有意义equals().

假设你有这个代码:

Foobar foobarA = ...;
Foobar foobarB = ...;
if (foobarA.equals(foobarB)) {
    doSomethingThatOnlyMakesSenseIfTheyAreEqual(...);
}

如果有其他线程可能会改变两个对象的相等性,那么将 synchronized 添加到 equals 方法将 而不是 确保doSomething...() 被调用时对象相等。

原因很简单:其他线程可以在 equals() 调用 returns true 之后更改关系 ,但是在 doSomething...() 通话之前或期间。

如果它需要同步,那么你必须像这样同步它:

Foobar foobarA = ...;
Foobar foobarB = ...;
Object lock = new Object();

synchronized(lock) {
    if (foobarA.equals(foobarB)) {
        doSomethingThatOnlyMakesSenseIfTheyAreEqual(...);
    }
}

当然,修改对象的代码也必须同步到同一个 lock