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
。
在调用 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
。