Why is using identityHashCode in listing 10.3
is valid. If there is two simultaneous requests:
A: transferMoney(myAccount, yourAccount, 10);
B: transferMoney(yourAccount, myAccount, 20);
then, there can be two objects of same account in memory. So assume, I have two threads, that create domain object of account 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
If identityHashCode does not depend on state of an Object, how can we ensure that System.identityHashCode(akk1Thread1) > System.identityHashCode(akk2Thread1) == System.identityHashCode(akk1Thread2) > System.identityHashCode(akk2Thread2)
?
Listing 10.3. Inducing a lock ordering to avoid deadlock.
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();
}
}
}
}
}
Should I accept, that this example is valid only when application has global cache of Accounts?
That is meant to guarantee the order of locking those two Account
objects is always the same, since identityHashCode
never changes for an object and we get an easy comparison of == same object, != different object (but we can artificially compare one as "lower" than the other, giving us order).
If we have multiple threads using the same Account
objects (possibly through a global cache, but that's an unnecessary detail) and for some reason the order of locking varies, there is the potential of a deadlock when one thread locks fromAcct
first and another locks toAcct
first, even though they're working on the same accounts.
So this guarantees that given two accounts, we can't get a deadlock and the code is thread-safe. If you don't share the objects between different threads, then there is no problem. But since this is JCIP, we can assume that accounts are shared and there are thousands of concurrent transactions (imagine a stock broker system for example, and accounts are cached and concurrently used).