我试图理解同步对象的概念。使用Java Cert Book中的这个例子,你能帮助我理解下面两段代码之间的行为差异(一个是我们要同步的对象,我们要保护的方法/操作不受竞争条件影响而另一个我们使用的代码一个辅助对象作为锁来实现相同的目标):
1
class Client {
BankAccount account;
public void updateTransaction() {
synchronized (account) {
account.update(); // update is safe because of lock on account obj
}
}
public double withdrawFunds() {
double amt;
synchronized (account) {
account.calculateInterest();
amt= account.withdraw();
}
return amt;
}
}
2
class Client {
BankAccount account;
Object lock = new Object();
public void updateTransaction() {
synchronized (lock) {
account.update(); // update is safe because of a lock
}
}
public double withdrawFunds() {
double amt;
synchronized (lock) {
account.calculateInterest();
amt= account.withdraw();
}
return amt;
}
}
答案 0 :(得分:3)
这两个都会抛出NullPointerExceptions,但只能在不同的地方。
您应始终在永远不为null(最终)的不可变引用上进行同步。
我想你的问题的答案是,对于给定的客户端,帐户可能会发生变化(假设有一个setter)但是锁永远不应该。同样,保证这一点的唯一方法是将其标记为最终。
答案 1 :(得分:2)
更好的方法是避免在封装时暴露锁定。
class Client {
final BankAccount account;
public void updateTransaction() {
account.update(); // update is synchonized.
}
public double withdrawFunds() {
return account.calculateInterestAndWithdraw();
}
}
答案 2 :(得分:1)
在这两种情况下,没有区别,但风格差异。
如果可以更改帐户,则可能存在可能在错误对象上同步的问题。让这个属性最终,你很高兴。
答案 3 :(得分:1)
不同之处在于谁持有锁。
第一种情况更好,因为对帐户的相同引用也可以在其他线程中共享,例如在类Manager
中,如果此管理员需要访问该帐户,则应尝试保留在Client
线程试图同时访问帐户的任何情况下锁定自己。
在第二个代码中,Manager
需要锁定lock
对象,以防止对数据造成损害,因此需要获取帐户和锁定。
最好的方法是将锁封装在帐户中。
答案 4 :(得分:1)
不同之处在于其他线程在您的系统中正在做什么。在第一种情况下,如果有任何原因导致某些其他逻辑(在某些其他代码中定义)不应与updateTransaction方法同时执行,则该逻辑也可以在帐户对象上同步。相反,如果某些不相关的代码在与updateTransaction无关时也使用account作为锁,则可能会出现错误同步。
在第二种情况下,锁定对象仅对Client类可见,因此可以保证唯一的同步是您在此类中指定的内容。
我同意Peter Lawrey的观点,最好的方法是将同步逻辑封装在一个有意义的地方,并使用私有/受保护的可见性锁定对象。
答案 5 :(得分:0)
我会使用选项2,只需将锁定对象设为最终。帐户对象可能会被更改,尤其是如果客户端可以有多个帐户。如果使用最终锁定对象,即使帐户对象更改为其他实例,也将确保对帐户的任何更改进行同步。