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

Programming Java threads in the real world, Part 2

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

  • Print
  • Feedback

Page 7 of 7

The other solution is to use an architecture that tends to minimize these sorts of problems. (Various threading architectures will be the subject of the remaining articles in this series.)

There is no magic bullet there. I've seen 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. I haven't actually used any of these products yet, but I remain skeptical.

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.) But as Doug Lea pointed out in a recent e-mail to me:

[the complaints] boiled down to "you tend to like best what you are most used to." Java makes some things that are painful in POSIX easy, and vice-versa. In any case, it is pretty easy to simulate one set of primitives with the other.


That's life, I guess.

The next several articles in this series on threads will present a solution to the problem that decouples the semaphores from the things they guard, but that solution introduces a whole set of additional problems.

Conclusion

Hopefully, I've 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. All is not lost, however. Over the next few months, I'll present a library of classes that solve many common threading problems, including some of the ones I've just discussed. Sometimes, I even think of it as fun, but maybe that's because I've been programming too long.

About the author

Allen Holub has been working in the computer industry since 1979. He is widely published in magazines (Dr. Dobb's Journal, Programmers Journal, Byte, MSJ, among others). He has seven books to his credit, and is currently working on an eighth that will present the complete sources for a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming (first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University of California Berkeley Extension since 1982. Allen offers both public classes and in-house training in Java and object-oriented design topics. He also does object-oriented design consulting. Get more information and contact Allen via his Web site http://www.holub.com.
  • Print
  • Feedback

Resources
  • 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 the first article in this series. For convenience, I've listed them 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 heavilyDoug Lea, Concurrent Programming in JavaDesign Principles and Patterns (ReadingAddison 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, seeScott 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 slantBill Lewis and Daniel J. Berg, Threads PrimerA Guide to Multithreaded Programming (Englewood CliffsPrentice Hall/SunSoft Press, ISBN 0-13-443698-9) http://www.sun.com/books/books/Lewis/Lewis.html