为什么在
listing 10.3
中使用identityHashCode是有效的。如果有两个同时请求:
A: transferMoney(myAccount, yourAccount, 10);
B: transferMoney(yourAccount, myAccount, 20);
那么,内存中可以存在两个同一个账户的对象。因此,假设我有两个线程,它们创建帐户的域对象
record Account(int accountNumber) { }
akk1Thread1 = new Account(1)
akk1Thread2 = new Account(2)
akk1Thread1.equals(akk1Thread2) // true
System.identityHashCode(akk1Thread1) - System.identityHashCode(akk1Thread2) // -227806817 not equals
如果identityHashCode不依赖于对象的状态,我们如何确保
System.identityHashCode(akk1Thread1) > System.identityHashCode(akk2Thread1) == System.identityHashCode(akk1Thread2) > System.identityHashCode(akk2Thread2)
?
清单 10.3。引入锁定顺序以避免死锁。
private static final Object tieLock = new Object();
public void transferMoney(final Account fromAcct,
final Account toAcct,
final DollarAmount amount)
throws InsufficientFundsException {
class Helper {
public void transfer() throws InsufficientFundsException {
if (fromAcct.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException();
} else {
fromAcct.debit(amount);
toAcct.credit(amount);
}
}
}
int fromHash = System.identityHashCode(fromAcct);
int toHash = System.identityHashCode(toAcct);
if (fromHash < toHash) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (toAcct) {
synchronized (fromAcct) {
new Helper().transfer();
}
}
} else {
synchronized (tieLock) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
}
}
}
我是否应该接受这个示例仅当应用程序具有帐户的全局缓存时才有效?
这意味着保证锁定这两个
Account
对象的顺序始终相同,因为 identityHashCode
对于对象来说永远不会改变,并且我们可以轻松比较 == 相同对象,!= 不同对象(但我们可以人为地将一个比另一个“低”,给我们排序)。
如果我们有多个线程使用相同的
Account
对象(可能通过全局缓存,但这是一个不必要的细节),并且由于某种原因锁定顺序不同,那么当一个线程锁定 fromAcct
时,可能会出现死锁第一个和另一个首先锁定 toAcct
,即使他们使用相同的帐户。
因此,这保证了给定两个帐户,我们不会出现死锁,并且代码是线程安全的。如果不在不同线程之间共享对象,那么就没有问题。但由于这是 JCIP,我们可以假设账户是共享的,并且有数千个并发交易(例如,想象一个股票经纪系统,账户被缓存并同时使用)。