Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

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

Avoid synchronization deadlocks

Use a consistent, defined synchronization ordering to keep your apps running

  • Print
  • Feedback

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.

How to avoid deadlocks

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.

Shrink synchronized blocks to avoid multiple locking

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.

A more sophisticated lock-ordering technique

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.)

  • Print
  • Feedback

Resources