|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 4
Listing 3. An even more subtle potential synchronization deadlock
public void transferMoney(Account fromAccount,
Account toAccount,
DollarAmount amountToTransfer) {
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.hasSufficientBalance(amountToTransfer) {
fromAccount.debit(amountToTransfer);
toAccount.credit(amountToTransfer);
}
}
}
}
Even if all methods that operate on two or more accounts use the same ordering, Listing 3 contains the seeds of the same deadlock problem as Listings 1 and 2, but in an even subtler way. Consider what happens when thread A executes:
transferMoney(accountOne, accountTwo, amount);
While at the same time, thread B executes:
transferMoney(accountTwo, accountOne, anotherAmount);
Again, the two threads try to acquire the same two locks, but in different orders; the deadlock risk still looms, but in a much less obvious form.
One of the best ways to prevent the potential for deadlock is to avoid acquiring more than one lock at a time, which is often practical. However, if that is not possible, you need a strategy that ensures you acquire multiple locks in a consistent, defined order.
Depending on how your program uses locks, it might not be complicated to ensure that you use a consistent locking order. In some programs, such as in Listing 1, all critical locks that might participate in multiple locking are drawn from a small set of singleton lock objects. In that case, you can define a lock acquisition ordering on the set of locks and ensure that you always acquire locks in that order. Once the lock order is defined, it simply needs to be well documented to encourage consistent use throughout the program.
In Listing 2, the problem grows more complicated because, as a result of calling a synchronized method, the locks are acquired
implicitly. You can usually avoid the sort of potential deadlocks that ensue from cases like Listing 2 by narrowing the synchronization's
scope to as small a block as possible. Does Model.updateModel() really need to hold the Model lock while it calls View.somethingChanged()? Often it does not; the entire method was likely synchronized as a shortcut, rather than because the entire method needed
to be synchronized. However, if you replace synchronized methods with smaller synchronized blocks inside the method, you must
document this locking behavior as part of the method's Javadoc. Callers need to know that they can call the method safely
without external synchronization. Callers should also know the method's locking behavior so they can ensure that locks are
acquired in a consistent order.
In other situations, like Listing 3's bank account example, applying the fixed-order rule grows even more complicated; you
need to define a total ordering on the set of objects eligible for locking and use this ordering to choose the sequence of
lock acquisition. This sounds messy, but is in fact straightforward. Listing 4 illustrates that technique; it uses a numeric
account number to induce an ordering on Account objects. (If the object you need to lock lacks a natural identity property like an account number, you can use the Object.identityHashCode() method to generate one instead.)