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 2 of 4

Testing for deadlocks is difficult, as deadlocks depend on timing, load, and environment, and thus might happen infrequently or only under certain circumstances. Code can have the potential for deadlock, like Listing 1, but not exhibit deadlock until some combination of random and nonrandom events occur, such as the program being subjected to a certain load level, run on a certain hardware configuration, or exposed to a certain mix of user actions and environmental conditions. Deadlocks resemble time bombs waiting to explode in our code; when they do, our programs simply hang.

Inconsistent lock ordering causes deadlocks

Fortunately, we can impose a relatively simple requirement on lock acquisition that can prevent synchronization deadlocks. Listing 1's methods have the potential for deadlock because each method acquires the two locks in a different order. If Listing 1 had been written so that each method acquired the two locks in the same order, two or more threads executing these methods could not deadlock, regardless of timing or other external factors, because no thread could acquire the second lock without already holding the first. If you can guarantee that locks will always be acquired in a consistent order, then your program will not deadlock.

Deadlocks are not always so obvious

Once attuned to the importance of lock ordering, you can easily recognize Listing 1's problem. However, analogous problems might prove less obvious: perhaps the two methods reside in separate classes, or maybe the locks involved are acquired implicitly through calling synchronized methods instead of explicitly via a synchronized block. Consider these two cooperating classes, Model and View, in a simplified MVC (Model-View-Controller) framework:

Listing 2. A more subtle potential synchronization deadlock

  public class Model { 
    private View myView;
    public synchronized void updateModel(Object someArg) { 
      doSomething(someArg);
      myView.somethingChanged();
    }
    public synchronized Object getSomething() { 
      return someMethod();
    }
  }
  public class View { 
    private Model underlyingModel;
    public synchronized void somethingChanged() { 
      doSomething();      
    }
    public synchronized void updateView() { 
      Object o = myModel.getSomething();
    }
  }


Listing 2 has two cooperating objects that have synchronized methods; each object calls the other's synchronized methods. This situation resembles Listing 1 -- two methods acquire locks on the same two objects, but in different orders. However, the inconsistent lock ordering in this example is much less obvious than that in Listing 1 because the lock acquisition is an implicit part of the method call. If one thread calls Model.updateModel() while another thread simultaneously calls View.updateView(), the first thread could obtain the Model's lock and wait for the View's lock, while the other obtains the View's lock and waits forever for the Model's lock.

You can bury the potential for synchronization deadlock even deeper. Consider this example: You have a method for transferring funds from one account to another. You want to acquire locks on both accounts before performing the transfer to ensure that the transfer is atomic. Consider this harmless-looking implementation:

  • Print
  • Feedback

Resources