Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
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 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.
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.
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: