New York University

Computer Science Department

Courant Institute of Mathematical Sciences

 

Session 5: Programming Java Threads, Part 2

(The perils of race conditions, deadlock, and other threading problems)

 

 

Introduction

 

This handout discusses the perils that can arise when you approach multithreading in a naive way. The handout assumes familiarity with the basic thread-related parts of Java: the synchronized keyword and monitors, wait(), notify(), the basics of using the Thread class, and so forth.

 

The notion of a race condition was introduced earlier as a situation whereby two threads simultaneously contend for the same object and, as a consequence, leave the object in an undefined state. Java's synchronized keyword is in the language to help avoid these problems. The present handout covers race conditions in depth, and also discusses various deadlock scenarios, which are related to race conditions but are much harder to find and debug.

 

Monitors, mutexes, and bathrooms

Just to make sure we're all starting from the same place, a little review of terms is in order. The central concept for synchronization in the Java model is the monitor, developed some 20 years ago by C. A. R. Hoare. A monitor is a body of code guarded by a mutual-exclusion semaphore (or, to use a term coined at Digital Equipment Corp., a mutex). The central notion of a mutex concerns ownership. Only one thread can own the mutex at a time. If a second thread tries to "acquire" ownership, it will block (be suspended) until the owning thread "releases" the mutex. If several threads are all waiting to acquire the same mutex, they will all be released simultaneously when the owning thread releases the mutex. The released threads will then have to sort out amongst themselves who gains ownership. (Typically, priority order, FIFO order, or some combination thereof is used to determine which thread gains control.) You guard a block of code by acquiring a mutex at the top of the block and releasing it at the bottom. The code comprising the monitor does not have to be contiguous: several discontiguous code blocks can all acquire and release the same mutex, in which case all of this code is considered to be in the same monitor because it uses a common mutex.

 

The best analogy for a monitor is an airplane bathroom. Only one person can be in the bathroom at a time (we hope). Everybody else is queued up in a rather narrow aisle waiting to use it. As long as the door is locked, the bathroom is unaccessible. Given these terms, in our analogy the object is the airplane, the bathroom is the monitor (assuming there's only one bathroom), and the lock on the door is the mutex.

 

In Java, every object has one and only one monitor and mutex associated with it. The single monitor has several doors into it, however, each indicated by the synchronized keyword. When a thread passes over the synchronized keyword, it effectively locks all the doors. Of course, if a thread doesn't pass across the synchronized keyword, it hasn't locked the door, and some other thread is free barge in at any time.

 

Bear in mind that the monitor is associated with the object, not the class. Several threads can all be executing the same method in parallel, but the receiving objects (as identified by the this references) have to be different. For example, several instances of a thread-safe queue could be in use by several threads. These threads could simultaneously enqueue objects to different queues, but they could not enqueue to the same queue at the same time. Only one thread at a time can be in the monitor for a given queue.

 

To refine the earlier analogy, the airplane is still the object, but the monitor is really all the bathrooms combined (each synchronized chunk of code is a bathroom). When a thread enters any bathroom, the doors on all the bathrooms are locked. Different instances of a class are different airplanes, however, and if the bathroom doors in your airplane are unlocked, you needn't really care about the state of the doors in the other airplanes.

 

Why is stop() deprecated in JDK 1.2?

The fact that the monitor is built into every Java object is actually somewhat controversial. Some of the problems associated with bundling the condition variable and mutex together inside every Java object have been fixed in JDK 1.2, by the simple expediency of deprecating the most problematic methods of the Thread class: stop() and suspend(). You can deadlock a thread if you call either of these methods from inside a synchronized method of your own. Look at the following method, remembering that the mutex is the lock on the door, and the thread that locks the door "owns" the monitor. That's why the stop() and suspend() methods are deprecated in JDK 1.2. Consider this method:

 

class some_class
{ //...

synchronized void f()
{ Thread.currentThread().stop();
}
}

 

Now consider what happens when a thread calls f(). The thread acquires the lock when entering the monitor but then stops. The mutex is not released by stop(). This is the equivalent of someone going into the bathroom, locking the door, and flushing himself down the toilet! Any other thread that now tries to call f() (or any other synchronized method of the class) on the same object is blocked forever. The suspend() method (which is also deprecated) has the same problem. The sleep() method (which is not deprecated) can be just as tricky. (Someone goes into the bathroom, locks the door, and falls asleep). Also remember that Java objects, even those that extend Thread or implement Runnable, continue to exist, even if the associated thread has stopped. You can indeed call a synchronized method on an object associated with a stopped thread. Be careful.

 

Race conditions and spin locks


A race condition occurs when two threads try to access the same object at the same time, and the behavior of the code changes depending on who wins. The following diagram shows a single (unsynchronized) object accessed simultaneously by multiple threads. A thread can be preempted in fred() after modifying one field but before modifying the other. If another thread comes along at that point and calls any of the methods shown, the object will be left in an unstable state, because the initial thread will eventually wake up and modify the other field.


 

 


Usually, you think of objects sending messages to other objects. In multithreaded environments, you must think about message handlers running on threads. Think: this thread causes this object to send a message to that object. A race condition can occur when two threads cause messages to be sent to the same object at the same time.

 

Working with wait() and notify()


The following code demonstrates a blocking queue used for one thread to notify another when some event occurs.  The basic notion is that a thread that tries to dequeue from an empty queue will block until some other thread puts something in the queue.

 

class Notifying_queue
{
private static final queue_size = 10;
private Object[] queue = new Object[ queue_size ];
private int head = 0;
private int tail = 0;
public void synchronized enqueue( Object item )
{
queue[++head %= queue_size ]= item;
this.notify(); // The "this" is there only to
} // improve readability.
public Object synchronized dequeue( )
{
try
{ if( head == tail ) //
<=== This is a bug
this.wait();
}
catch( InterruptedException e )
{
// If we get here, we were not actually notified.
// returning null doesn't indicate that the
// queue is empty, only that the waiting was
// abandoned.

return null;
}

return queue[++tail %= queue_size ];
}
}

 

Starting with an empty queue, let's follow the sequence of operations in play if one thread does a dequeue and then another (at some later time) enqueues an item.

 

1.     The dequeueing thread calls dequeue(), entering the monitor (and locking out other threads) until the monitor is released. The thread tests for an empty queue (head==tailwait(). (we will explain why this is a bug in a moment.)

2.     The wait() releases the lock. (The current thread temporarily leaves the monitor.) It then blocks on a second synchronization object called a condition variable. (The basic notion of a condition variable is that a thread blocks until some condition becomes true. In the case of Java's built-in condition variable, the thread will wait until the notified condition is set to true by some other thread calling notify.) It's important to realize that the waiting thread has released the monitor at this juncture.

3.     A second thread now comes along and enqueues an object, eventually calling notify(), thereby releasing the waiting (dequeueing) thread.

4.     The dequeueing thread now has to wait to reacquire the monitor before wait() can return, so it blocks again, this time on the lock associated with the monitor.

5.     The en queueing thread returns from the enqueue() method releasing the monitor.

6.     The dequeueing thread acquires the monitor, wait() returns, an object is dequeued, and dequeue() returns, releasing the monitor.

 

Race conditions following a wait()

Now, what sorts of problems can arise? The main ones are unexpected race conditions. First, what if you replaced the notify() call with a call to notifyAll()? Imagine that several threads were simultaneously trying to dequeue something from the same, empty, queue. All of these threads are blocked on a single condition variable, waiting for an enqueue operation to be executed.

 

When enqueue() sets the condition to true by calling notifyAll(), the threads are all released from the wait (moved from a suspended to a runnable state). The released threads all try to acquire the monitor at once, but only one would "win" and get the enqueued object. The problem is that the others would then get the monitor too, in some undefined sequence, after the winning thread had dequeued the object and returned from dequeue(). Since these other threads don't retest for an empty queue, they will dequeue garbage. (Looking at the code, the dequeue() method will advance the tail pointer to the next slot, the contents of which are undefined since the queue is empty.)

 

Fix the problem by replacing the if statement with a while loop (called a spin lock). Now the threads that don't get the object will go back to waiting:

 

public Object synchronized dequeue( )
{
try
{
while( head == tail ) // used to be an if
this.wait();
}
catch( InterruptedException e )
{ return null;
}

return queue[++tail %= queue_size ];
}

 

That while loop also solves another, less obvious problem. What if we leave the notify() statement in place, and don't put in a notifyAll()? Since notify() releases only one waiting thread, won't that solve the problem? It turns out that the answer is no. Here's what can happen:

 

1.     notify() is called by the enqueueing thread, releasing the condition variable.

2.     The dequeueing thread is then preempted, after being released from the wait on the condition variable, but before it tries to reacquire the monitor's lock.

3.     A second thread calls dequeue() at exactly this point, and successfully enters the monitor because no other threads are officially waiting. This second thread successfully dequeues the object; wait() is never called since the queue isn't empty.

4.     The original thread is now allowed to run, it acquires the monitor, doesn't test for empty a second time, and then dequeues garbage.

 

This second scenario is easily fixed the same way as the first: replace the if statement with a while loop.

 

Note that the Java specification does not require that wait() be implemented as an atomic operation (that is, one that can't be preempted while moving from the condition variable to the monitor's lock). This means that using an if statement instead of a while loop might work in some Java implementations, but the behavior is really undefined. Using a spin lock instead of a simple if statement is cheap insurance against implementations that don't treat wait() as atomic.

 

Threads are not objects


Now let's move on to harder-to-find problems. The first difficulty is the commonplace confusion of threads and objects: Methods run on threads, objects do not. Put another way, the only way to get a method to run on a given thread is to call it (either directly or indirectly) from that thread's run() method. Simply putting it into the Thread derivative is not enough. For example, look at the following simple thread (which just prints its fields every so often):

 

class My_thread extends Thread
{
private int field_1 = 0;
private int field_2 = 0;

public void run()
{
setDaemon(true); // this thread will not keep the app alive

while( true )
{
System.out.println( " field_1=" + field_1
" field_2=" + field_2 );
sleep(100);
}
}

synchronized public void modify( int new_value )
{ field_1 = new_value;
field_2 = new_value;
}
}

 

You could start up the thread and send it a message like this:

 

My_thread test = new My_thread;

test.start();
//...
test.modify(1);

 

The only functions that run on the new thread are run() itself and println() (which run() calls). The modify() method never runs on the same thread as the println call; rather, it runs on whatever thread was running when the call was made. (In this case, it runs on whatever thread main() is running on.) Depending on timing, the earlier fragment could print:

 

field_1=0, field2=0

 

But it could just as easily print:

 

field_1=0, field2=1

 

or

 

field_1=1, field2=1

 

There's just no telling. (In the first and last cases, the thread would have been outside the println statement when modify() was called. In the second example, the thread would have been halfway through evaluating the arguments to println(), having fetched field_1, but not field_2. It prints the unmodified field_1 and the modified field_2.

 

The main issue here is that there is no simple solution to this problem. The modify() method is indeed synchronized in the earlier example, but run() can't be. Were it synchronized, you'd enter the monitor (and lock the object), when you started up the thread. Thereafter, any other thread that called any synchronized method on the object (such as modify()) would block until the monitor was released. Since run() doesn't return (as is often the case), the release will never happen, and the thread will act like a black hole, sucking up any other thread that calls any of its synchronized methods. In the current example, the main thread would be suspended, and the program would hang. So just using the synchronized keyword in a naive way gets us nowhere.

 

The deadlock scenario

Synchronizing run() is a good example of a simple deadlock scenario, where a thread is blocked forever, waiting for something to happen that can't. Let's look at a few examples that are more realistic than this.

 

The most common deadlock scenario occurs when two threads are both waiting for each other to do something. The following (admittedly contrived) code snippet makes what's going on painfully obvious:

 

class Flintstone
{
int field_1; private Object lock_1 = new int[1];
int field_2; private Object lock_2 = new int[1];

public void fred( int value )
{ synchronized( lock_1 )
{ synchronized( lock_2 )
{
field_1 = 0;
field_2 = 0;
}
}
}

public void barney( int value )
{ synchronized( lock_2 )
{ synchronized( lock_1 )
{
field_1 = 0;
field_2 = 0;
}
}
}
}

 

Now, imagine a scenario whereby one thread (call it Wilma) calls fred(), passes through the synchronization of lock_1, and is then preempted, allowing another thread (call it Betty) to execute. Betty calls barney(), acquires lock_2, and tries to acquire lock_1, but can't because Wilma has it. Betty is now blocked, waiting for lock_1 to become available, so Wilma wakes up and tries to acquire lock_2 but can't because Betty has it. Wilma and Betty are now deadlocked. Neither one can ever execute.

 

(Note that lock_1 and lock_2 have to be one-element arrays rather than simple ints, because only objects have monitors in Java; the argument to synchronized must be an object. An array is a first-class object in Java; a primitive-type such as int is not. Consequently, you can synchronize on it. Moreover, a one-element array is efficient to bring into existence compared to a more elaborate object (like an Integer) since it's both small and does not require a constructor call. Also, note that a reference to the lock is kept as a simple Object reference, since the array elements will never be accessed.)

 

As mentioned above, Wilma and Betty are a contrived example, but the multiple-lock situation comes up frequently.

 

Get out the magnifying glass


If all deadlock scenarios were as easy to recognize as Wilma and Betty, deadlock wouldn't be a problem. Consider the following code, though:

 

class Boss
{
private Side_kick robin;

public synchronized void set_side_kick( Side_kick kid_in_tights )
{ robin = kid_in_tights;
};

public synchronized void to_the_bat_cave()
{ robin.get_in_the_car();
}

public synchronized void okay() // sent to us by robin
{ //...
}

public synchronized void hold_on() // sent to us by robin
{ //...
}
}
//-------------------------------------------------------
class Side_kick
{
private Boss batman;

public synchronized void set_boss( Boss guy_in_cape )
{ batman = guy_in_cape;
}

public synchronized void get_in_the_car() // sent by batman
{ batman.okay();
}

public synchronized void sock_bam_pow() // sent from outside
{ batman.hold_on();
}
}
//-------------------------------------------------------
class Gotham_city
{ static Boss batman = new Boss();
static Side_kick robin = new Side_kick();

public static void main( String[] args )
{
batman.set_side_kick( robin );
robin.set_boss( batman );

// spawn off a bunch of threads that use batman and robin.
}
}

 

Now imagine the following:

 

1.     One thread (call it Alfred) issues a to_the_bat_cave() request to the batman object passed to it from main().

2.     The batman object starts to process the method, but is preempted just before it calls robin.get_in_the_car(). At this juncture, Alfred has acquired the lock for the batman object.

3.     Now along comes a second thread (call it Joker), which issues a sock_bam_pow() message to the robin object that it got from main().

4.     The robin object (whose sock_bam_pow() method is running on the Joker thread) tries to send a hold_on() message to batman, but can't because Alfred owns the lock on batman. So the Joker thread is now blocked, waiting for Alfred to release the lock on batman.

5.     Now Alfred gets a chance to run, and it tries to send a get_in_the_car() message to the robin object, but it can't because the Joker thread owns the lock on robin. Both threads are now deadlocked (sound familiar?)

 

Remember: threads own the locks and methods execute on threads, not objects.

 

This situation is, of course, much harder to see than the Wilma-and-Betty problem because the locks in the batman-robin example are the natural locks associated with individual objects. There are no standalone synchronized statements in the batman-robin code, and the locks are associated with two completely distinct objects.

 

Multithreaded programmers tear their hair out looking for the causes of these sorts of problems, and there are only two solutions. The first is to thoroughly design the code before implementing it, and then to really study both the design and the implementation before you even think about running it the first time. When writing  multithreaded programs, much more time should be spent on code and design reviews than on coding.

 

The other solution is to use a threading architecture that tends to minimize these sorts of problems.

 

There is no magic bullet there. There are ads for a couple of products that instrument a VM in a way that, in theory, will detect deadlock and race conditions. The problem, though, is a classic Heisenberg-uncertainty dilemma: there's no way to observe the process without impacting it. If the problem is timing-related, adding a print statement or changing to a debugging version of the VM will change the timing, perhaps eliminating the problem. As a result, you should remain skeptical with respect to the advertised products.

 

Nested-monitor lockout


Another important form of deadlock is not discussed much in the Java-language books: nested-monitor lockout. This problem usually occurs when you call a blocking function from within a synchronized method, and the only way to release the block is to call another synchronized method. The following code demonstrates the problem.

 

class Notifying_queue
{ // Same class as was described earlier, blocks on dequeue from
// an empty queue.
//...
}

class Black_hole
{
private Notifying_queue queue = new Notifying_queue(5);

public synchronized void put( Object thing )
{ queue.enqueue( thing );
}

public synchronized Object get( )
{ return queue.dequeue();
}
}

Consider what happens when you try to dequeue something from an empty queue:

 

1.     You call get() to get the item from the queue.

2.     get() is synchronized, so the Black_hole is now locked.

3.     get() calls dequeue(), which blocks, waiting for some other thread to enqueue something. get() does not return.

4.     Another thread tries to enqueue something, but the only way to enqueue something is by calling put, which we can't do because the Black_hole is locked. That is, any thread that tries to put() will block because the first thread has not returned from get() yet.

 

The Black_hole now sucks up all threads that try to put() or get() anything. They all block forever.

 

Depending on where this occurs, the black hole could suck up every thread in your program. Also bear in mind that this problem can occur anytime you have a blocking call (such as a file read) inside a synchronized method.

 

The only cures are:

 

1.     Don't make blocking calls in synchronized methods

2.     Make sure there's a way to talk to the blocking object via another class or a nonsynchronized method

 

In the current situation, you could just not synchronize put(), but that wouldn't work in a more realistic situation where put() accessed fields of the class that were accessed by other methods.

 

This problem has been known since Java 1.0 was in the early prerelease stage, and several people complained vociferously about it. (The problem is a direct result of the way Java's synchronization works -- the condition variable and mutex are both part of the object and not separate entities --- compounded by the fact that you have to acquire the mutex to wait on the condition.) A possible solution to the problem is to decouple the semaphores from the things they guard, but that solution introduces a whole set of additional problems.

 

Conclusion


Hopefully, we have demonstrated by now that programming in a multithreaded environment isn't as easy as the evangelist types would have you believe. Java provides platform-independent ways to use the two essential synchronization mechanisms: exclusion semaphores and condition variables. It does it in an awkward way, however, that doesn't help much when you're trying to do more than blink your logo in a simple applet.

 

References

 

·        Sun's Technical Articles page has several articles on multithreading
http://developer.javasoft.com/developer/technicalArticles/#thread

·        Prashant Jain and Douglas C. Schmidt have a good article contrasting C++ to Java that discusses many of the thread-related problems inherent in the language. The article can be found at

http://www.cs.wustl.edu/%7Eschmidt/C++2java.html

·        Doug Lea has a bunch of Mutex and Condition-variable classes in his util.concurrent package. See

     http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html

·        Doug Schmidt's Ace Framework is a good, though complex, attempt at a truly platform-independent threading system

http://www.cs.wustl.edu/~schmidt/

 

There are several good books that discuss the Java threading issues mentioned in part 1 of the handout. For convenience, there are listed again here:

 

·        For a great in-depth look at multithreading in general and the implementation of multithreading both in and with Java in particular, this is a must. It's required reading if you're using threads heavily: Doug Lea, Concurrent Programming in Java: Design Principles and Patterns (Reading: Addison Wesley, 1997)

http://java.sun.com/docs/books/cp/

·        For a book on Java threading that is less technical but more readable than Lea's, see: Scott Oaks and Henry Wong, Java Threads (Sebastopol, Calif.: O'Reilly, 1997)

http://www.oreilly.com/catalog/jthreads/

·        This book is good for the general subject of multithreading but doesn't have a Java slant: Bill Lewis and Daniel J. Berg, Threads Primer: A Guide to Multithreaded Programming (Englewood Cliffs: Prentice Hall/SunSoft Press, ISBN 0-13-443698-9)

http://www.sun.com/books/books/Lewis/Lewis.html