Java对象锁定等于还是同步等于?(对hashcode也有效)

Java对象锁定等于还是同步等于?(对hashcode也有效),java,multithreading,equals,hashcode,Java,Multithreading,Equals,Hashcode,在调用equals方法期间,建议采用什么方法进行同步,是覆盖equals并添加关键字synchronized,还是在调用equals时获得两个对象上的锁更好 例如,我有: public class EqualsTesterHelper { public int test = 0; @Override public boolean equals(Object obj) { if (!(obj instanceof EqualsTesterHelper)

在调用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;
        }   
    }
}
还是在我的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();
            }
        }
    }
我看了一下,但似乎唯一的答案是,您应该确保在计算equals(或hashcode)时不会修改对象,这不会提示哪种解决方案更好


Edit:实际上我意识到我的“synchronized”equals有缺陷,因为我仍然需要在方法中传递的对象上进行同步。

同步
equals()
没有意义

假设您有以下代码:

Foobar foobarA = ...;
Foobar foobarB = ...;
if (foobarA.equals(foobarB)) {
    doSomethingThatOnlyMakesSenseIfTheyAreEqual(...);
}
如果有其他线程可能会改变两个对象的相等性,那么在调用
doSomething…()
时,将
synchronized
添加到
equals
方法将无法确保对象相等

原因很简单:在
equals()
调用返回
true
之后,但在
doSomething…()
调用之前或期间,其他线程可能会更改关系

如果需要同步,则必须按如下方式进行同步:

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

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

当然,修改对象的代码也必须在同一个
锁上同步

同步
equals()
没有意义

假设您有以下代码:

Foobar foobarA = ...;
Foobar foobarB = ...;
if (foobarA.equals(foobarB)) {
    doSomethingThatOnlyMakesSenseIfTheyAreEqual(...);
}
如果有其他线程可能会改变两个对象的相等性,那么在调用
doSomething…()
时,将
synchronized
添加到
equals
方法将无法确保对象相等

原因很简单:在
equals()
调用返回
true
之后,但在
doSomething…()
调用之前或期间,其他线程可能会更改关系

如果需要同步,则必须按如下方式进行同步:

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

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

当然,修改对象的代码也必须在同一个
锁上同步

为什么要同步
equals
hashCode
?这些方法的实现是否会更改对象的状态?这取决于…..最好使
等于
hashCode
取决于实例的不可变属性,原因不是多线程。例如,如果
hashCode
可以更改,您可能无法在
HashSet
中找到它。@mike:当equals计算可变字段时,该字段可以由并发线程更改。@asettouf在这种情况下,您应该使用外部同步,正如Javadoc中所述,为什么要同步
等于
hashCode
?这些方法的实现是否会更改对象的状态?这取决于…..最好使
等于
hashCode
取决于实例的不可变属性,原因不是多线程。例如,如果
hashCode
可以更改,您可能无法在
HashSet
中找到该字段。@mike:当equals计算可变字段时,该字段可以由并发线程更改。@asettouf在这种情况下,您应该使用外部同步,如Javadoc中所述。您有一点,虽然我会假设,如果使用equals方法在测试中的某个时刻测试相等性,您可能不需要“做一些有意义的事情”@asettouf当有多个线程在不同步的情况下运行时,您如何定义“某个时刻”?换句话说,如果线程A知道两个对象在其过去的某个时刻是相等的,但它永远无法知道它们现在是否相等,那么线程A知道这两个对象是相等的有什么用呢?不要忘记,非同步线程不一定会看到事情以相同的顺序发生。如果无法知道它们在其他线程的过去是否相等,那么知道它们在线程A过去的某个时间点相等又有什么意义呢?假设您有一个对象,您从HTTP请求创建了第二个对象,该对象应与第一个对象相等,您定义了10秒的超时,然后检查两者是否相等。在equals调用过程中,如果对象不相等,在“10秒+delta t”时,您可以创建对象,equals仍然返回true,尽管10秒的超时已经过去(很公平,这显然是一个边缘情况,这是我的问题),同时使
equals
同步可以防止修改错误(即,如果您的
equals
方法检查某个内部值是否为null,如果不是null,则该方法基于值不为null的信念进行操作,其他线程将内部状态更改为null可能会抛出难以调试的
NullPointerException
)…你说得有道理,不过我想如果你的equals方法用于测试某个时刻的相等性,你可能不需要“做一些有意义的事情”@asettouf你如何定义“某个时刻”当有多个线程在没有同步的情况下运行时?换句话说,如果线程A知道两个对象在过去的某个时刻是相等的,如果它永远不知道它们现在是否相等,那么线程A又有什么用呢?别忘了,没有同步的线程不一定看到事情以相同的顺序发生。这有什么意义在知道它们在线程A的过去的某个点上是相等的,如果没有办法知道它们在其他线程的过去是否是相等的?假设你有一个对象,你从一个HTTP请求创建了第二个对象