Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Java World Articles (www.javaworld.com)Programming Java ThreadsIn The Real WorldAllen Hollub                              ...
ContentsProgramming Java threads in the real world, Part 1 ..................................................................
Until next time .............................................................................................................
Notifier-side problems: notifications in a multithreaded world ........................................ 124     Mysteries ...
All Java programs other than simple console-based applications are multithreaded, whetheryou like it or not. The problem i...
// and method arguments, but does not access        // any fields of the class (or call any methods        // that access ...
}}Though the HotSpot VM is supposed to address the synchronization-overhead problem,HotSpot isnt a freebee -- you have to ...
Though Java permits threading to be implemented entirely in the VM, at least in theory, thisapproach would preclude any pa...
just an address space. It has no priority per se, and is not scheduled. The system schedulesthreads; then, if a given thre...
Cooperate!There are typically two threading models supported by operating systems: cooperative andpreemptive.The cooperati...
provides the best (or worst) of all worlds by supporting both cooperative and preemptivemodels in the same program.Mapping...
can execute in parallel), but the threads within the process are cooperative (and executeconcurrently).A process isnt limi...
This threading model gives you an enormous amount of flexibility. You can choose between anextremely fast (but strictly co...
looks at C++ as a bad dream, the memory of which is mercifully fading. Hes been teachingprogramming (first C, then C++ and...
Programming Java threads in the real world, Part 2The perils of race conditions, deadlock, and other threading problemsIn ...
thread doesnt pass across the synchronized keyword, it hasnt locked the door, and some otherthread is free barge in at any...
stopped. You can indeed call a synchronized method on an object associated with a stoppedthread. Be careful.Race condition...
want to keep things simple.) The basic notion is that a thread that tries to dequeue from anempty queue will block until s...
4. The dequeueing thread now has to wait to reacquire the monitor before wait() can       return, so it blocks again, this...
That while loop also solves another, less obvious problem. What if we leave the notify()statement in place, and dont put i...
sleep(100);      }    }    synchronized public void modify( int new_value )    { field_1 = new_value;         field_2 = ne...
monitor was released. Since run() doesnt return (as is often the case), the release will neverhappen, and the thread will ...
(Note that lock_1 and lock_2 have to be one-element arrays rather than simple ints, because onlyobjects have monitors in J...
class Gotham_city{ static Boss batman = new Boss();   static Side_kick robin = new Side_kick();   public static void main(...
Page 7 of 7The other solution is to use an architecture that tends to minimize these sorts of problems.(Various threading ...
4. Another thread tries to enqueue something, but the only way to enqueue something is       by calling put, which we cant...
Hopefully, Ive demonstrated by now that programming in a multithreaded environment isnt aseasy as the evangelist types wou...
This book is good for the general subject of multithreading but doesnt have a JavaslantBill Lewis and Daniel J. Berg, Thre...
Programming Java threads in the real world, Part 3Roll-your-own mutexes and centralized lock managementBy Allen Holub, Jav...
x ); }21| public void use_everything_else(){ do_something_with( b +y ); }22| }As it stands, this code is a multithreading ...
19| public void use_x_and_y(){ synchronized(xy_lock){/*...*/ } }20|21| // partition 3, functions use a, b, x, and y22|23| ...
everything). Ive used a one-element array for my lock, rather than something like an Integer,because arrays come into exis...
Any semaphore that implements Semaphore can be locked in groups using the Lock_manager classshown in Listing 2. There are ...
45|             // exception to terminate the program.46|47|             throw new Error( e.toString() );48|         }49| ...
The problem is complicated a bit by arrays of objects of some arbitrary class. How can a genericsort utility figure out th...
earlier) really depends on the situation. The main advantage is in being able to use theLock_manager.acquire_multiple() to...
18|   public      int id() { return _id;   }19|20|   /**21|   * Acquire the mutex. The mutex can be acquired multiple time...
71|     public synchronized void release()72|     {73|       if( owner != Thread.currentThread() )74|          throw new O...
My Mutex class implements a "recursive" mutex. The "owner" thread can acquire the mutexmore than once, but it must release...
Allen Holub has been working in the computer industry since 1979. He is widely published inmagazines (Dr. Dobbs Journal, P...
Programming Java threads in the real world, Part 4Condition variables and counting semaphores -- filling in a few chinks i...
Joe Bowbeer pointed out (quite correctly):Why not advise the use of try/finally to prevent an exception from gunking up th...
making the loop terminate for timeout values less than Integer.MAX_VALUE (the value I use for"forever"). It continues to u...
Finally, in last months column, I inadvertently used an outdated version of the Lock_managerscomparator class. (It threw a...
11 |         }12 |       }13 |   );14 |15 |    add(input);16 |   pack();17 |    show();18 | }19 |20 | String read_line(){ ...
20 | }21 |22 | String synchronized read_line(){ return entered; }23 | //...24 | }Note that the inner-class method has to s...
The synchronized(text_has_been_entered) statement on line 15 is mandatory, since entering thesynchronized block puts us in...
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
Upcoming SlideShare
Loading in …5
×

17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub

3,269 views

Published on

Published in: Technology, News & Politics
  • Be the first to comment

17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub

  1. 1. Java World Articles (www.javaworld.com)Programming Java ThreadsIn The Real WorldAllen Hollub 98
  2. 2. ContentsProgramming Java threads in the real world, Part 1 ....................................................................4 Platform dependence...........................................................................................................5 Atomic energy ......................................................................................................................5 Concurrency versus parallelism ............................................................................................7 Get your priorities straight ...................................................................................................8 Cooperate! .........................................................................................................................10 Mapping kernel threads to user processes .........................................................................11 Wrapping it up ...................................................................................................................13 About the author ...............................................................................................................13Programming Java threads in the real world, Part 2 ..................................................................15 The perils of race conditions, deadlock, and other threading problems ..............................15 Monitors, mutexes, and bathrooms ...................................................................................15 Why is stop() deprecated in JDK 1.2? .................................................................................16 Race conditions and spin locks ...........................................................................................17 Working with wait() and notify() ........................................................................................17 Threads are not objects......................................................................................................20 The deadlock scenario ........................................................................................................22 Get out the magnifying glass ..............................................................................................23 Nested-monitor lockout .....................................................................................................25 Conclusion .........................................................................................................................26 About the author ...............................................................................................................27 Resources ..........................................................................................................................27Programming Java threads in the real world, Part 3 ..................................................................29 Roll-your-own mutexes and centralized lock management ................................................29 When synchronized isnt good enough .............................................................................29 So, roll your own ................................................................................................................30 Semaphores .......................................................................................................................32 Managing the locks ............................................................................................................32 A manageable mutex .........................................................................................................35
  3. 3. Until next time ...................................................................................................................39 About the author ...............................................................................................................39Programming Java threads in the real world, Part 4 ..................................................................41 Condition variables and counting semaphores -- filling in a few chinks in Javas threading model ................................................................................................................................41 Oops! Could my code have bugs in it? ................................................................................41 Condition variables ............................................................................................................44 Counting semaphores ........................................................................................................52 Wrapping up ......................................................................................................................56 About the author ...............................................................................................................57Programming Java threads in the real world, Part 5 ..................................................................58 Has Sun abandoned run anywhere? Plus: Threads and Swing, timers, and getting around stop(), suspend(), and resume() deprecation ......................................................................58 Whatever happened to run anywhere? ............................................................................58 Back to threads ..................................................................................................................59 Swingin threads .................................................................................................................60 Using the Swing Timer........................................................................................................62 Roll your own .....................................................................................................................70 How does an Alarm work? .................................................................................................71 Happy trails ........................................................................................................................88 Back to threads ..................................................................................................................89 Swingin threads .................................................................................................................90 Using the Swing Timer........................................................................................................92 Roll your own ................................................................................................................... 100 How does an Alarm work? ............................................................................................... 102 Happy trails ...................................................................................................................... 118Programming Java threads in the real world, Part 6 ................................................................ 120 The Observer pattern and mysteries of the AWTEventMulticaster ................................... 120 Implementing Observer in a multithreaded world ............................................................ 120 Observer-side problems: inner-class synchronization ....................................................... 121
  4. 4. Notifier-side problems: notifications in a multithreaded world ........................................ 124 Mysteries of the AWTEventMulticaster ............................................................................ 133 Wrapping up .................................................................................................................... 141Programming Java threads in the real world, Part 7 ................................................................ 143 Singletons, critical sections, and reader/writer locks ........................................................ 143 Critical sections, singletons, and the Class object ............................................................. 143 Singletons ........................................................................................................................ 146 Reader/writer locks .......................................................................................................... 154 Its a wrap ........................................................................................................................ 164 About the author ............................................................................................................. 164Programming Java threads in the real world, Part 8 ................................................................ 166 Threads in an object-oriented world, thread pools, implementing socket accept loops .. 166 Synchronous vs. asynchronous messages ......................................................................... 166 The thread-per-method solution ...................................................................................... 167 Thread pools and blocking queues ................................................................................... 172 Sockets and thread pools ................................................................................................. 183 Conclusion ....................................................................................................................... 193 About the author ............................................................................................................. 193Programming Java threads in the real world, Part 9 ................................................................ 195 More threads in an object-oriented world: Synchronous dispatchers, active objects, detangling console I/O ..................................................................................................... 195 Synchronous dispatching ................................................................................................. 195 Active objects................................................................................................................... 205 Wrapping things up, books, and JavaOne ......................................................................... 219 And now for something completely different... ................................................................ 219Programming Java threads in the real world, Part 1By Allen Holub, JavaWorld.com, 10/01/98
  5. 5. All Java programs other than simple console-based applications are multithreaded, whetheryou like it or not. The problem is that the Abstract Windowing Toolkit (AWT) processesoperating system (OS) events on its own thread, so your listener methods actually run on theAWT thread. These same listener methods typically access objects that are also accessed fromthe main thread. It may be tempting, at this point, to bury your head in the sand and pretendyou dont have to worry about threading issues, but you cant usually get away with it. And,unfortunately, virtually none of the books on Java addresses threading issues in sufficientdepth. (For a list of helpful books on the topic, see Resources.)This article is the first in a series that will present real-world solutions to the problems ofprogramming Java in a multithreaded environment. Its geared to Java programmers whounderstand the language-level stuff (the synchronized keyword and the various facilities of theThread class), but want to learn how to use these language features effectively.Platform dependenceUnfortunately, Javas promise of platform independence falls flat on its face in the threadsarena. Though its possible to write a platform-independent multithreaded Java program, youhave to do it with your eyes open. This isnt really Javas fault; its almost impossible to write atruly platform-independent threading system. (Doug Schmidts ACE [Adaptive CommunicationEnvironment] framework is a good, though complex, attempt. See Resources for a link to hisprogram.) So, before I can talk about hard-core Java-programming issues in subsequentinstallments, I have to discuss the difficulties introduced by the platforms on which the Javavirtual machine (JVM) might run.Atomic energyThe first OS-level concept thats important to understand is atomicity. An atomic operationcannot be interrupted by another thread. Java does define at least a few atomic operations. Inparticular, assignment to variables of any type except long or double is atomic. You dont have toworry about a thread preempting a method in the middle of the assignment. In practice, thismeans that you never have to synchronize a method that does nothing but return the value of(or assign a value to) a boolean or int instance variable. Similarly, a method that did a lot ofcomputation using only local variables and arguments, and which assigned the results of thatcomputation to an instance variable as the last thing it did, would not have to be synchronized.For example:class some_class{ int some_field; void f( some_class arg ) // deliberately not synchronized { // Do lots of stuff here that uses local variables
  6. 6. // and method arguments, but does not access // any fields of the class (or call any methods // that access any fields of the class). // ... some_field = new_value; // do this last. }}On the other hand, when executing x=++y or x+=y, you could be preempted after the incrementbut before the assignment. To get atomicity in this situation, youll need to use the keywordsynchronized.All this is important because the overhead of synchronization can be nontrivial, and can varyfrom OS to OS. The following program demonstrates the problem. Each loop repetitively calls amethod that performs the same operations, but one of the methods (locking()) is synchronizedand the other (not_locking()) isnt. Using the JDK "performance-pack" VM running under WindowsNT 4, the program reports a 1.2-second difference in runtime between the two loops, or about1.2 microseconds per call. This difference may not seem like much, but it represent a 7.25-percent increase in calling time. Of course, the percentage increase falls off as the method doesmore work, but a significant number of methods -- in my programs, at least -- are only a fewlines of code.import java.util.*;class synch{ synchronized int locking (int a, int b){return a + b;} int not_locking (int a, int b){return a + b;} private static final int ITERATIONS = 1000000; static public void main(String[] args) { synch tester = new synch(); double start = new Date().getTime(); for(long i = ITERATIONS; --i >= 0 ;) tester.locking(0,0); double end = new Date().getTime(); double locking_time = end - start; start = new Date().getTime(); for(long i = ITERATIONS; --i >= 0 ;) tester.not_locking(0,0); end = new Date().getTime(); double not_locking_time = end - start; double time_in_synchronization = locking_time - not_locking_time; System.out.println( "Time lost to synchronization (millis.): " + time_in_synchronization ); System.out.println( "Locking overhead per call: " + (time_in_synchronization / ITERATIONS) ); System.out.println( not_locking_time/locking_time * 100.0 + "% increase" );
  7. 7. }}Though the HotSpot VM is supposed to address the synchronization-overhead problem,HotSpot isnt a freebee -- you have to buy it. Unless you license and ship HotSpot with your app,theres no telling what VM will be on the target platform, and of course you want as little aspossible of the execution speed of your program to be dependent on the VM thats executing it.Even if deadlock problems (which Ill discuss in the next installment of this series) didnt exist,the notion that you should "synchronize everything" is just plain wrong-headed.Concurrency versus parallelismThe next OS-related issue (and the main problem when it comes to writing platform-independent Java) has to do with the notions of concurrency and parallelism. Concurrentmultithreading systems give the appearance of several tasks executing at once, but these tasksare actually split up into chunks that share the processor with chunks from other tasks. Thefollowing figure illustrates the issues. In parallel systems, two tasks are actually performedsimultaneously. Parallelism requires a multiple-CPU system.Unless youre spending a lot of time blocked, waiting for I/O operations to complete, a programthat uses multiple concurrent threads will often run slower than an equivalent single-threadedprogram, although it will often be better organized than the equivalent single-thread version. Aprogram that uses multiple threads running in parallel on multiple processors will run muchfaster.Page 2 of 6
  8. 8. Though Java permits threading to be implemented entirely in the VM, at least in theory, thisapproach would preclude any parallelism in your application. If no operating-system-levelthreads were used, the OS would look at the VM instance as a single-threaded application,which would most likely be scheduled to a single processor. The net result would be that notwo Java threads running under the same VM instance would ever run in parallel, even if youhad multiple CPUs and your VM was the only active process. Two instances of the VM runningseparate applications could run in parallel, of course, but I want to do better than that. To getparallelism, the VM must map Java threads through to OS threads; so, you cant afford to ignorethe differences between the various threading models if platform independence is important.Get your priorities straightIll demonstrate the ways the issues I just discussed can impact your programs by comparingtwo operating systems: Solaris and Windows NT.Java, in theory at least, provides ten priority levels for threads. (If two or more threads are bothwaiting to run, the one with the highest priority level will execute.) In Solaris, which supports231 priority levels, this is no problem (though Solaris priorities can be tricky to use -- more onthis in a moment). NT, on the other hand, has seven priority levels available, and these have tobe mapped into Javas ten. This mapping is undefined, so lots of possibilities presentthemselves. (For example, Java priority levels 1 and 2 might both map to NT priority level 1, andJava priority levels 8, 9, and 10 might all map to NT level 7.)NTs paucity of priority levels is a problem if you want to use priority to control scheduling.Things are made even more complicated by the fact that priority levels arent fixed. NT providesa mechanism called priority boosting, which you can turn off with a C system call, but not fromJava. When priority boosting is enabled, NT boosts a threads priority by an indeterminateamount for an indeterminate amount of time every time it executes certain I/O-related systemcalls. In practice, this means that a threads priority level could be higher than you thinkbecause that thread happened to perform an I/O operation at an awkward time.The point of the priority boosting is to prevent threads that are doing background processingfrom impacting the apparent responsiveness of UI-heavy tasks. Other operating systems havemore-sophisticated algorithms that typically lower the priority of background processes. Thedownside of this scheme, particularly when implemented on a per-thread rather than a per-process level, is that its very difficult to use priority to determine when a particular thread willrun.It gets worse.In Solaris, as is the case in all Unix systems, processes have priority as well as threads. Thethreads of high-priority processes cant be interrupted by the threads of low-priority processes.Moreover, the priority level of a given process can be limited by a system administrator so thata user process wont interrupt critical OS processes. NT supports none of this. An NT process is
  9. 9. just an address space. It has no priority per se, and is not scheduled. The system schedulesthreads; then, if a given thread is running under a process that isnt in memory, the process isswapped in. NT thread priorities fall into various "priority classes," that are distributed across acontinuum of actual priorities. The system looks like this: Windows NTs priority architectureThe columns are actual priority levels, only 22 of which must be shared by all applications. (Theothers are used by NT itself.) The rows are priority classes. The threads running in a processpegged at the idle priority class are running at levels 1 through 6 and 15, depending on theirassigned logical priority level. The threads of a process pegged as normal priority class will runat levels 1, 6 through 10, or 15 if the process doesnt have the input focus. If it does have theinput focus, the threads run at levels 1, 7 through 11, or 15. This means that a high-prioritythread of an idle priority class process can preempt a low-priority thread of a normal priorityclass process, but only if that process is running in the background. Notice that a processrunning in the "high" priority class only has six priority levels available to it. The other classeshave seven.NT provides no way to limit the priority class of a process. Any thread on any process on themachine can take over control of the box at any time by boosting its own priority class; there isno defense against this.The technical term I use to describe NTs priority is unholy mess. In practice, priority is virtuallyworthless under NT.So whats a programmer to do? Between NTs limited number of priority levels and itsuncontrollable priority boosting, theres no absolutely safe way for a Java program to usepriority levels for scheduling. One workable compromise is to restrict yourself toThread.MAX_PRIORITY, Thread.MIN_PRIORITY, and Thread.NORM_PRIORITY when you call setPriority(). Thisrestriction at least avoids the 10-levels-mapped-to-7-levels problem. I suppose you could usethe os.name system property to detect NT, and then call a native method to turn off priorityboosting, but that wont work if your app is running under Internet Explorer unless you also useSuns VM plug-in. (Microsofts VM uses a nonstandard native-method implementation.) In anyevent, I hate to use native methods. I usually avoid the problem as much as possible by puttingmost threads at NORM_PRIORITY and using scheduling mechanisms other than priority. (Illdiscuss some of these in future installments of this series.)
  10. 10. Cooperate!There are typically two threading models supported by operating systems: cooperative andpreemptive.The cooperative multithreading modelIn a cooperative system, a thread retains control of its processor until it decides to give it up(which might be never). The various threads have to cooperate with each other or all but one ofthe threads will be "starved" (meaning, never given a chance to run). Scheduling in mostcooperative systems is done strictly by priority level. When the current thread gives up control,the highest-priority waiting thread gets control. (An exception to this rule is Windows 3.x, whichuses a cooperative model but doesnt have much of a scheduler. The window that has the focusgets control.)The main advantage of cooperative multithreading is that its very fast and has a very lowoverhead. For example, a context swap -- a transfer of control from one thread to another --can be performed entirely by a user-mode subroutine library without entering the OS kernel.(In NT, which is something of a worst-case, entering the kernel wastes 600 machine cycles. Auser-mode context swap in a cooperative system does little more than a C setjump/longjump callwould do.) You can have thousands of threads in your applications significantly impactingperformance. Since you dont lose control involuntarily in cooperative systems, you dont haveto worry about synchronization either. That is, you never have to worry about an atomicoperation being interrupted. The main disadvantage of the cooperative model is that its verydifficult to program cooperative systems. Lengthy operations have to be manually divided intosmaller chunks, which often must interact in complex ways.Page 4 of 6The preemptive multithreading modelThe alternative to a cooperative model is a preemptive one, where some sort of timer is usedby the operating system itself to cause a context swap. The interval between timer ticks iscalled a time slice. Preemptive systems are less efficient than cooperative ones because thethread management must be done by the operating-system kernel, but theyre easier toprogram (with the exception of synchronization issues) and tend to be more reliable sincestarvation is less of a problem. The most important advantage to preemptive systems isparallelism. Since cooperative threads are scheduled by a user-level subroutine library, not bythe OS, the best you can get with a cooperative model is concurrency. To get parallelism, the OSmust do the scheduling. Of course, four threads running in parallel will run much faster than thesame four threads running concurrently.Some operating systems, like Windows 3.1, only support cooperative multithreading. Others,like NT, support only preemptive threading. (You can simulate cooperative threading in NT witha user-mode library like the "fiber" library, but fibers arent fully integrated into the OS.) Solaris
  11. 11. provides the best (or worst) of all worlds by supporting both cooperative and preemptivemodels in the same program.Mapping kernel threads to user processesThe final OS issue has to do with the way in which kernel-level threads are mapped into user-mode processes. NT uses a one-to-one model, illustrated in the following picture.NT user-mode threads effectively are kernel threads. They are mapped by the OS directly ontoa processor and they are always preemptive. All thread manipulation and synchronization aredone via kernel calls (with a 600-machine-cycle overhead for every call). This is astraightforward model, but is neither flexible nor efficient.The Solaris model, pictured below, is more interesting. Solaris adds to the notion of a thread,the notion of a lightweight process (LWP). The LWP is a schedulable unit on which one or morethreads can run. Parallel processing is done on the LWP level. Normally, LWPs reside in a pool,and they are assigned to particular processors as necessary. An LWP can be "bound" to aspecific processor if its doing something particularly time critical, however, thereby preventingother LWPs from using that processor.Up at the user level, you have a system of cooperative, or "green," threads. In a simplesituation, a process will have one LWP shared by all the green threads. The threads must yieldcontrol to each other voluntarily, but the single LWP the threads share can be preempted by anLWP in another process. This way the processes are preemptive with respect to each other (and
  12. 12. can execute in parallel), but the threads within the process are cooperative (and executeconcurrently).A process isnt limited to a single LWP, however. The green threads can share a pool of LWPs ina single process. The green threads can be attached (or "bound") to an LWP in two ways:Page 5 of 6 1. The programmer explicitly "binds" one or more threads to a specific LWP. In this case, the threads sharing a LWP must cooperate with each other, but they can preempt (or be preempted by) threads bound to a different LWP. If every green thread was bound to a single LWP, youd have an NT-style preemptive system. 2. The threads are bound to green threads by the user-mode scheduler. This is something of a worst case from a programming point of view because you cant assume a cooperative or a preemptive environment. You may have to yield to other threads if theres only one LWP in the pool, but you might also be preempted.
  13. 13. This threading model gives you an enormous amount of flexibility. You can choose between anextremely fast (but strictly concurrent) cooperative system, a slower (but parallel) preemptivesystem, or any combination of the two.So why does this matter to a Java programmer? The main issue is that the choice of threadingmodel is entirely up to the VM -- you have no control. For example, early versions of the SolarisVM were strictly cooperative. Java threads were all green threads sharing a single LWP. Thecurrent version of the Solaris VM, however, uses several LWPs. Similarly, the NT VMs dont havethe equivalent of green threads, so theyre always preemptive. In order to write platform-independent code, you must make two seemingly contradictory assumptions: 1. You can be preempted by another thread at any time. You must use the synchronized keyword carefully to assure that non-atomic operations work correctly. 2. You will never be preempted unless you give up control. You must occasionally perform some operation that will give control to other threads so they can have a chance to run. Use yield() and sleep() in appropriate places (or make blocking I/O calls). For example, you might want to consider calling yield() every one hundred iterations or so of a long loop, or voluntarily going to sleep for a few milliseconds every so often to give lower-priority threads a chance to run. (yield() will yield control only to threads running at your priority level or higher).Wrapping it upSo, those are the main OS-level issues you must consider when youre writing a Java program.Since you can make no assumptions about your operating environment, you have to programfor the worst case. For example, you have to assume you can be preempted at any time, so youmust use synchronized appropriately, but you must also assume that you will never bepreempted, so you must also use yield(), sleep(), or occasionally blocking I/O calls to permit otherthreads to run. You cant assume priority levels 1 and 2 are different. They might not be afterNT has mapped Javas 10 levels into its 7 levels. You cant assume that a priority level 2 threadwill always be higher priority than one that runs at level 1.Subsequent articles will get into considerable detail about various thread-related programmingproblems and solutions. Heres the roadmap for the rest of the series:About the authorAllen Holub has been working in the computer industry since 1979. He is widely published inmagazines (Dr. Dobbs Journal, Programmers Journal, Byte, and MSJ, among others). He hasseven books to his credit, and is currently working on an eighth that will present the completesources for a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now
  14. 14. looks at C++ as a bad dream, the memory of which is mercifully fading. Hes been teachingprogramming (first C, then C++ and MFC, now OO-Design and Java) both on his own and for theUniversity 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 designconsulting. Get information, and contact Allen, via his Web site http://www.holub.com.Page 6 of 6 1. Deadlock, starvation, and nested-monitor lockout 2. Roll-your-own mutexes and a deadlock-handling lock manager 3. Counting semaphores, condition variables, and singletons 4. Event notification in a multithreaded environment (the mysteries of the AWTEventMulticaster) 5. Reader/writer locks 6. Timers 7. Synchronous-dispatching: multithreading without threads 8. Implementing the active-object patternResources For a great in-depth look at multithreading in general and the implementation of multithreading both in and with Java in particular, this ones a must. Its required reading if youre using threads heavilyDoug Lea, Concurrent Programming in JavaDesign Principles and Patterns (Addison Wesley, 1997). http://java.sun.com/docs/books/cp/ For an intro-level book on Java threading that is less technical but more readable than Leas effort, seeScott Oaks and Henry Wong, Java Threads (OReilly, 1997) http://www.oreilly.com/catalog/jthreads/ This book is good for those looking into the general subject of multithreading, but doesnt have a Java slantBill Lewis and Daniel J. Berg, Threads PrimerA Guide to Multithreaded Programming (Prentice Hall/SunSoft Press, ISBN 0-13-443698-9) http://www.sun.com/books/books/Lewis/Lewis.html Doug Schmidts ACE framework is a good, though complex, attempt at a truly platform- independent threading system http://www.cs.wustl.edu/~schmidt/
  15. 15. Programming Java threads in the real world, Part 2The perils of race conditions, deadlock, and other threading problemsIn his recent " Design for thread safety" article (part of the Design Techniques column), BillVenners introduced the notion of a race condition, a situation whereby two threadssimultaneously contend for the same object and, as a consequence, leave the object in anundefined state. Bill pointed out that Javas synchronized keyword is in the language to help avoidthese problems, and he provided a straightforward example of its use. Last months article wasthe first in a series on threads. The present article continues that series, covering raceconditions in depth. It also discusses various deadlock scenarios, which are related to raceconditions but are much harder to find and debug. This month, well focus primarily on theproblems encountered when programming Java threads. Subsequent Java Toolbox columns willfocus entirely on solutions.Monitors, mutexes, and bathroomsJust to make sure were all starting from the same place, a little review of terms is in order. Thecentral concept for synchronization in the Java model is the monitor, developed some 20 yearsago 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 mutexconcerns 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 releasedsimultaneously when the owning thread releases the mutex. The released threads will thenhave to sort out amongst themselves who gains ownership. (Typically, priority order, FIFOorder, or some combination thereof is used to determine which thread gains control.) Youguard a block of code by acquiring a mutex at the top of the block and releasing it at thebottom. The code comprising the monitor does not have to be contiguous: severaldiscontiguous code blocks can all acquire and release the same mutex, in which case all of thiscode is considered to be in the same monitor because it uses a common mutex.The best analogy Ive heard for a monitor is an airplane bathroom. Only one person can be inthe bathroom at a time (we hope). Everybody else is queued up in a rather narrow aisle waitingto use it. As long as the door is locked, the bathroom is unaccessible. Given these terms, in ouranalogy the object is the airplane, the bathroom is the monitor (assuming theres only onebathroom), 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 singlemonitor has several doors into it, however, each indicated by the synchronized keyword. When athread passes over the synchronized keyword, it effectively locks all the doors. Of course, if a
  16. 16. thread doesnt pass across the synchronized keyword, it hasnt locked the door, and some otherthread is free barge in at any time.Bear in mind that the monitor is associated with the object, not the class. Several threads canall be executing the same method in parallel, but the receiving objects (as identified by the thisreferences) have to be different. For example, several instances of a thread-safe queue couldbe in use by several threads. These threads could simultaneously enqueue objects to differentqueues, but they could not enqueue to the same queue at the same time. Only one thread at atime can be in the monitor for a given queue.Page 2 of 7To refine the earlier analogy, the airplane is still the object, but the monitor is really all thebathrooms combined (each synchronized chunk of code is a bathroom). When a thread entersany bathroom, the doors on all the bathrooms are locked. Different instances of a class aredifferent airplanes, however, and if the bathroom doors in your airplane are unlocked, youneednt 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 togetherinside every Java object have been fixed in JDK 1.2, by the simple expediency of deprecating themost problematic methods of the Thread class: stop() and suspend(). You can deadlock a thread ifyou call either of these methods from inside a synchronized method of your own. Look at thefollowing method, remembering that the mutex is the lock on the door, and the thread thatlocks the door "owns" the monitor. Thats why the stop() and suspend() methods are deprecatedin 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 enteringthe monitor but then stops. The mutex is not released by stop(). This is the equivalent ofsomeone going into the bathroom, locking the door, and flushing himself down the toilet! Anyother thread that now tries to call f() (or any other synchronized method of the class) on thesame object is blocked forever. The suspend() method (which is also deprecated) has the sameproblem. The sleep() method (which is not deprecated) can be just as tricky. (Someone goes intothe bathroom, locks the door, and falls asleep). Also remember that Java objects, even thosethat extend Thread or implement Runnable, continue to exist, even if the associated thread has
  17. 17. stopped. You can indeed call a synchronized method on an object associated with a stoppedthread. Be careful.Race conditions and spin locksA race condition occurs when two threads try to access the same object at the same time, andthe behavior of the code changes depending on who wins. The following diagram shows asingle (unsynchronized) object accessed simultaneously by multiple threads. A thread can bepreempted in fred() after modifying one field but before modifying the other. If another threadcomes along at that point and calls any of the methods shown, the object will be left in anunstable 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 multithreadedenvironments, you must think about message handlers running on threads. Think: this threadcauses this object to send a message to that object. A race condition can occur when twothreads cause messages to be sent to the same object at the same time.Page 3 of 7Working with wait() and notify()The following code demonstrates a blocking queue used for one thread to notify another whensome event occurs. (Well see a more realistic version of these in a future article, but for now I
  18. 18. want to keep things simple.) The basic notion is that a thread that tries to dequeue from anempty 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 doesnt indicate that the // queue is empty, only that the waiting was // abandoned. return null; } return queue[++tail %= queue_size ]; }}Starting with an empty queue, lets follow the sequence of operations in play if one thread doesa 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(). (Ill 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 Javas built-in condition variable, the thread will wait until the notified condition is set to true by some other thread calling notify.) Its 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.
  19. 19. 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 weresimultaneously trying to dequeue something from the same, empty, queue. All of these threadsare 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 fromthe wait (moved from a suspended to a runnable state). The released threads all try to acquirethe monitor at once, but only one would "win" and get the enqueued object. The problem isthat the others would then get the monitor too, in some undefined sequence, after the winningthread had dequeued the object and returned from dequeue(). Since these other threads dontretest 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 sincethe queue is empty.)Page 4 of 7Fix the problem by replacing the if statement with a while loop (called a spin lock). Now thethreads that dont 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 ]; }
  20. 20. That while loop also solves another, less obvious problem. What if we leave the notify()statement in place, and dont put in a notifyAll()? Since notify() releases only one waiting thread,wont that solve the problem? It turns out that the answer is no. Heres 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 monitors 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 isnt empty. 4. The original thread is now allowed to run, it acquires the monitor, doesnt 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 awhile loop.Note that the Java specification does not require that wait() be implemented as an atomicoperation (that is, one that cant be preempted while moving from the condition variable to themonitors lock). This means that using an if statement instead of a while loop might work insome Java implementations, but the behavior is really undefined. Using a spin lock instead of asimple if statement is cheap insurance against implementations that dont treat wait() as atomic.Threads are not objectsNow lets move on to harder-to-find problems. The first difficulty is the commonplace confusionof threads and objects: Methods run on threads, objects do not. Put another way, the only wayto get a method to run on a given thread is to call it (either directly or indirectly) from thatthreads 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 );
  21. 21. 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). Themodify() method never runs on the same thread as the println call; rather, it runs on whateverthread was running when the call was made. (In this case, it runs on whatever thread main() isrunning on.) Depending on timing, the earlier fragment could print:Page 5 of 7field_1=0, field2=0But it could just as easily print:field_1=0, field2=1orfield_1=1, field2=1Theres just no telling. (In the first and last cases, the thread would have been outside the printlnstatement when modify() was called. In the second example, the thread would have beenhalfway through evaluating the arguments to println(), having fetched field_1, but not field_2. Itprints 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 isindeed synchronized in the earlier example, but run() cant be. Were it synchronized, youd enterthe monitor (and lock the object), when you started up the thread. Thereafter, any other threadthat called any synchronized method on the object (such as modify()) would block until the
  22. 22. monitor was released. Since run() doesnt return (as is often the case), the release will neverhappen, and the thread will act like a black hole, sucking up any other thread that calls any ofits synchronized methods. In the current example, the main thread would be suspended, andthe program would hang. So just using the synchronized keyword in a naive way gets us nowhere.The deadlock scenarioSynchronizing run() is a good example of a simple deadlock scenario, where a thread is blockedforever, waiting for something to happen that cant. Lets look at a few examples that are morerealistic than this.The most common deadlock scenario occurs when two threads are both waiting for each otherto do something. The following (admittedly contrived) code snippet makes whats going onpainfully 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 thesynchronization of lock_1, and is then preempted, allowing another thread (call it Betty) toexecute. Betty calls barney(), acquires lock_2, and tries to acquire lock_1, but cant because Wilmahas it. Betty is now blocked, waiting for lock_1 to become available, so Wilma wakes up and triesto acquire lock_2 but cant because Betty has it. Wilma and Betty are now deadlocked. Neitherone can ever execute.
  23. 23. (Note that lock_1 and lock_2 have to be one-element arrays rather than simple ints, because onlyobjects 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 moreelaborate object (like an Integer) since its both small and does not require a constructor call.Also, note that I can keep the reference to the lock as a simple Object reference, since Ill neveraccess the array elements.)Page 6 of 7As I mentioned above, Wilma and Betty are a contrived example, but the multiple-lock situationcomes up frequently. Ill give a more detailed example next month.Get out the magnifying glassIf all deadlock scenarios were as easy to recognize as Wilma and Betty, deadlock wouldnt be aproblem. 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(); }}//-------------------------------------------------------
  24. 24. 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 cant 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 cant 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 thelocks in the batman-robin example are the natural locks associated with individual objects. Thereare no standalone synchronized statements in the batman-robin code, and the locks areassociated with two completely distinct objects.Multithreaded programmers tear their hair out looking for the causes of these sorts ofproblems, and there are only two solutions. The first is to thoroughly design the code beforeimplementing it, and then to really study both the design and the implementation before youeven think about running it the first time. When I do multithreaded programs, I spend muchmore time on code and design reviews than I spend on coding.
  25. 25. Page 7 of 7The 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. Ive seen ads for a couple of products that instrument a VM in away that, in theory, will detect deadlock and race conditions. The problem, though, is a classicHeisenberg-uncertainty dilemma: theres no way to observe the process without impacting it. Ifthe problem is timing-related, adding a print statement or changing to a debugging version ofthe VM will change the timing, perhaps eliminating the problem. I havent actually used any ofthese products yet, but I remain skeptical.Nested-monitor lockoutAnother 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 asynchronized method, and the only way to release the block is to call another synchronizedmethod. 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.
  26. 26. 4. Another thread tries to enqueue something, but the only way to enqueue something is by calling put, which we cant 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 fileread) inside a synchronized method.The only cures are: 1. Dont make blocking calls in synchronized methods 2. Make sure theres a way to talk to the blocking object via another class or a nonsynchronized methodIn the current situation, you could just not synchronize put(), but that wouldnt work in a morerealistic 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 severalpeople complained vociferously about it. (The problem is a direct result of the way Javassynchronization works -- the condition variable and mutex are both part of the object and notseparate entities --- compounded by the fact that you have to acquire the mutex to wait on thecondition.) 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 makessome things that are painful in POSIX easy, and vice-versa. In any case, it is pretty easy tosimulate one set of primitives with the other.Thats life, I guess.The next several articles in this series on threads will present a solution to the problem thatdecouples the semaphores from the things they guard, but that solution introduces a whole setof additional problems.Conclusion
  27. 27. Hopefully, Ive demonstrated by now that programming in a multithreaded environment isnt aseasy as the evangelist types would have you believe. Java provides platform-independent waysto use the two essential synchronization mechanisms: exclusion semaphores and conditionvariables. It does it in an awkward way, however, that doesnt help much when youre trying todo more than blink your logo in a simple applet. All is not lost, however. Over the next fewmonths, Ill present a library of classes that solve many common threading problems, includingsome of the ones Ive just discussed. Sometimes, I even think of it as fun, but maybe thatsbecause Ive been programming too long.About the authorAllen Holub has been working in the computer industry since 1979. He is widely published inmagazines (Dr. Dobbs Journal, Programmers Journal, Byte, MSJ, among others). He has sevenbooks to his credit, and is currently working on an eighth that will present the complete sourcesfor a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now looks atC++ as a bad dream, the memory of which is mercifully fading. Hes been teaching programming(first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University ofCalifornia Berkeley Extension since 1982. Allen offers both public classes and in-house trainingin Java and object-oriented design topics. He also does object-oriented design consulting. Getmore information and contact Allen via his Web site http://www.holub.com.Resources Suns 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 Schmidts 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, Ive 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. Its required reading if youre 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 Leas, seeScott Oaks and Henry Wong, Java Threads (Sebastopol, Calif.OReilly, 1997) http://www.oreilly.com/catalog/jthreads/
  28. 28. This book is good for the general subject of multithreading but doesnt have a JavaslantBill 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
  29. 29. Programming Java threads in the real world, Part 3Roll-your-own mutexes and centralized lock managementBy Allen Holub, JavaWorld.com, 11/01/98In last months column, I demonstrated a simple deadlock scenario using two nested synchronizedblocks that acquired the same two locks, but in a different order. (Please review last monthsexample if this isnt fresh in your mind.) This month, Ill take a look at a solution to thiscommonplace deadlock problem, presenting a roll-your-own exclusion semaphore class and alock manager that supports the safe acquisition of multiple semaphores. Using these objectsrather than the built-in synchronized can save you hours of searching for unexpected deadlocks.(They dont solve every possible deadlock problem, of course, but are nonetheless prettyuseful.)When synchronized isnt good enoughThe nested-synchronized-statements example from last month was -- admittedly -- contrived, butthe multiple-lock situation comes up frequently in the real world. One common problem is thetoo-coarse, object-level granularity of the synchronized keyword: theres only one monitor perobject, and sometimes thats not enough.Consider the following class, the methods of which can be broken up into three partitions. Themethods in the first partition use only a subset of the fields in the class. The methods in thesecond partition use a non-overlapping subset; they share fields that are not used by methodsin the first partition. The methods in the third partition use everything.1| class Complicated // NOT thread safe2| {3| private long a, b;4| private long x, y;5|6| // partition 1, functions use a and/or b7|8| public void use_a() { do_something_with(a); ); }9| public void use_b() { do_something_with(b); ); }10 public void use_a_and_b() { do_something_with(a+b); ); }11|12| // partition 2, functions use x and/or y13|14| public void use_x() { do_something_with(x); ); }15| public void use_y() { do_something_with(y); ); }16| public void use_x_and_y() { do_something_with(x+y); ); }17|18| // partition 3, functions use a, b, x, and y19|20| public void use_everything() { do_something_with( a +
  30. 30. x ); }21| public void use_everything_else(){ do_something_with( b +y ); }22| }As it stands, this code is a multithreading disaster. Nothing is synchronized and we haveguaranteed race conditions. (A race condition occurs when two threads try to access the sameobject at the same time, and chance determines which one wins the "race." Programs shouldntwork by chance.) Synchronizing all the methods would fix the problem, but then you couldntcall a method in partition 1 (emphasized in the code above) simply because some thread wasusing a method from partition 2 above. Since these two partitions dont interact with eachother, this solution imposes needless access restrictions on the methods of the class. If youreaccessing any method in partition 3, though, you do want to lock out everything in the othertwo partitions. We really need two locks in this situation. One to lock partition-1 variables andanother for partition-2 variables. The methods in partition 3 can then grab both locks.Page 2 of 5So, roll your ownYou can implement the two-lock strategy by synchronizing on something other than thecontaining object. Rather than synchronizing the methods, you can define two local variables touse as locks and synchronize on them:1| class Complicated_and_thread_safe2| {3| private long a, b;4| private long x, y;5|6| private Object ab_lock = new int[];7| private Object xy_lock = new int[];8|9| // partition 1, functions use a and/or b10|11| public void use_a() { synchronized(ab_lock){ /*...*/ } }12| public void use_b() { synchronized(ab_lock){/*...*/ } }13| public void use_a_and_b(){ synchronized(ab_lock){/*...*/ } }14|15| // partition 2, functions use x and/or y16|17| public void use_x() { synchronized(xy_lock){ /*...*/ } }18| public void use_y() { synchronized(xy_lock){/*...*/ } }
  31. 31. 19| public void use_x_and_y(){ synchronized(xy_lock){/*...*/ } }20|21| // partition 3, functions use a, b, x, and y22|23| public void use_everything()24| { synchronized(ab_lock) // grab both locks25| { synchronized(xy_lock)26| { /*...*/27| }28| }29| }30|31| public void use_everything_else()32| { synchronized(ab_lock)33| { synchronized(xy_lock)34| { /*...*/35| }36| }37| }38| }I havent synchronized the methods themselves in this example. (Remember, synchronizing amethod is effectively the same thing as wrapping all the code in the method in asynchronized(this){...} block.) Instead, Im providing a unique lock for each partition ( ab_lock andxy_lock) and then explicitly synchronizing on these individual locks.Java associates locks with objects (instance of some class that extends Object, so I cant useprimitive-type variables as locks here. I dont want to spend unnecessary time callingconstructors and doing other initialization operations on complicated objects, however.Consequently, the locks themselves are declared as the simplest possible Object -- an array.Arrays in Java are first-class objects: they implicitly extend the Object class. (If you dont believeme, compile the following code:1| public class foo2| { static Object ref = new int[]{ 10 };3|4| public static void main( String[] args )5| { System.out.println( ref.toString() );6| }7| }The class compiles just fine. Not only does the implicit cast from the array to Object work(because Object is a base class of all arrays), but the println() correctly invokes the compiler-generated toString() override (which prints absolutely nothing useful -- but you cant have
  32. 32. everything). Ive used a one-element array for my lock, rather than something like an Integer,because arrays come into existence very efficiently. For example, theres no constructor call.In the foregoing example, its critical that methods that acquire both locks always acquire themin the same order, otherwise we end up in the Wilma-and-Betty deadlock scenario discussedlast month. Acquiring multiple locks is a commonplace enough problem that some operatingsystems have system calls for this purpose. It would be nice to have an easy way to acquiremultiple locks, in Java, without having to worry about the order-of-acquisition problem. Theremainder of this months column describes one way to do that.Page 3 of 5SemaphoresListing 1 shows the core interface I use for all my roll-your-own semaphore classes: theSemaphore. Its in the com.holub.asynch package, as are all the thread-related classes andinterfaces Ill be discussing. (Ive also put all the code that appears in the listings into the"Goodies" section on my Web site; see Resources for the link.)1| package com.holub.asynch;2|3| interface Semaphore4| {5| int id ();6| void acquire(long timeout) throws InterruptedException;7| void release();8| }Listing 1: Semaphore.javaIf you havent worked much with semaphores, a semaphore has to have two properties in orderto be useful: 1. It must be able to identify itself using a unique ID when passed an id() request. The current implementation uses a unique integer for the purpose. 2. You must be able to acquire and release the semaphore, though the semantic meaning of "acquire" and "release" can vary, depending on what sort of semaphore youre dealing with.Managing the locks
  33. 33. Any semaphore that implements Semaphore can be locked in groups using the Lock_manager classshown in Listing 2. There are several things going on here. First of all, Ive used the Arrays.sort()method, one of the new JDK 1.2 data-structure facilities, to make life easier. The Arrays class is a"Booch utility" -- a class that contains nothing but static methods. The java.lang.Math utility isanother example. In fact, the Lock_manager itself is a utility, since its made up solely of staticmethods.01| package com.holub.asynch;02|03| import com.holub.asynch.Semaphore;04| import java.util.Arrays;05| import java.util.Comparator;06|07| class Lock_manager08| {09| private static int[] id_lock = new int[1];10| private static int id_pool = 0;11|12| public static int new_id( )13| {14| int id;15| synchronized( id_lock ) {16| id = id_pool++; }17| return id;18| }19|20| /**21| * Every mutex is given a unique int ID when its created.22| * This function returns once all of the locks in the incoming23| * array have been successfully acquired. Locks are always24| * acquired in ascending order of ID to attempt to avoid25| * deadlock situations.26| *27| * @param locks All of these locks must be acquired before28| * acquire_multiple returns.29| * @param timeout Maximum time to wait to acquire each30| * lock (milliseconds). The total time for the multiple31| * acquire operation could be (timeout * locks.length).32| **/33|34| public static void acquire_multiple( Semaphore[] locks, long timeout )35| throws InterruptedException36| { try37| {38| Arrays.sort(locks, new Lock_comparator() );39| for( int i = 0; i < locks.length; ++i )40| locks[i].acquire( timeout ) ;41| }42| catch( Comparable.Not_comparable e )43| {44| // shouldnt happen. Convert it to an uncaught
  34. 34. 45| // exception to terminate the program.46|47| throw new Error( e.toString() );48| }49| }50|51| private static class Lock_comparator implements Comparator52| { public int compare(Object a, Object b)53| throws Comparable.Not_comparable54| { return ((Semaphore)a).id() - ((Semaphore)b).id();55| }56| public boolean equals(Object obj)57| { return obj instanceof Lock_comparator;58| }59| }60| }Listing 2: Lock_manager.java"Booch utilities"Digressing for a moment, in most object-oriented languages, utilities are kludges needed to getaround design problems in the language itself or in existing libraries. The problem in Java is thatneither arrays nor primitive types really are true objects. If they were, they would contain allthe methods needed to manipulate them. For example, you would be able to find a cosineusing:int x = PI;x.cosine();Youd also be able to extend int, and so forth. In the case of arrays, you ought to be able to sortthem by asking them to sort themselves, like this:int array[] = new int[];//...array.sort();Unfortunately, Java isnt a "pure" object-oriented language in which everything is an object, sowe have to make do with a utility class.You can use the Arrays utility as follows to sort an array of int:int array[] = new int[];//...Arrays.sort( array );
  35. 35. The problem is complicated a bit by arrays of objects of some arbitrary class. How can a genericsort utility figure out the correct ordering? The Object class contains an equals() function, butwed need a greater_than() as well to do sorting.The Strategy patternTo the rescue comes the "Strategy" pattern. The notion is to pass into a method or objectanother object that encapsulates the strategy needed to do something. There are lots ofexamples of Strategy in Java. A java.awt.LayoutManager, for example, encapsulates the strategythat a Frame uses for laying itself out. You can change the layout simply by changing thisstrategy. You dont have to define a new kind of Frame class using derivation or some othermechanism. This, in turn, makes Frame much more "reusable" since the behavior of a Frame canbe changed without changing any of the code that comprises Frame.The Arrays utility uses the Strategy pattern to define a comparison strategy. In Listing 2, theLock_comparator (at the bottom of the listing) implements the comparison strategy byimplementing the new (to JDK 1.2) java.util.Comparator interface and its compare() method. (Thisworks like Cs strcmp(): it returns a negative number, zero, or a positive number depending onwhether a is less than, equal to, or greater than b.) An instance of the Comparator is passed tothe Arrays.sort() method like this:Arrays.sort(locks, new Lock_comparator() );The Lock_comparator objects compare() method is called by Arrays.sort() when it needs to compareobjects. C programmers will recognize this approach as very similar to that of qsort(), which ispassed a pointer to a compare method -- another example of Strategy.How Lock_manager worksNow, lets look at what the Lock_manager actually does. It starts out with the static new_id()method that returns a unique int every time its called. This method will be called from thevarious objects that implement the Semaphore interface to get the value theyll use for their ID.The acquire multiple method is used to safely acquire groups of semaphores. You pass it an arrayof objects that implement Semaphore, it sorts the array by ID, and then acquires the semaphoresone at a time in ID order. Consistent acquisition of multiple semaphores in ID order effectivelyeliminates the deadlock scenario weve been discussing.Page 4 of 5A manageable mutexNow lets move on and look at the other side of the equation: a class that implements asemaphore. The Mutex class in Listing 3 implements a simple mutual-exclusion semaphore.Whether it makes sense to use it (as compared to synchronizing on a lock object, as I did
  36. 36. earlier) really depends on the situation. The main advantage is in being able to use theLock_manager.acquire_multiple() to acquire several mutexes without worrying about an order-of-acquisition deadlock. I personally think the code also looks a little cleaner. I prefer this:1| class some_class2| {3| private Mutex lock = new Mutex();4|5| public void f()6| { lock.acquire();7| //...8| lock.release();9| }10| }to this:1| class some_class2| {3| private Object lock = new int[1];4|5| public void f()6| { synchronized( lock )7| {8| //...9| }10| }11| }even though its possible to forget the explicit release().1| package com.holub.asynch;2|3| import com.holub.asynch.Semaphore;4|5| import com.holub.tools.Comparable;6| import com.holub.tools.Sort;7|8| // Implementation of a mutual-exclusion semaphore. It can be owned by9| // only one thread at a time. The thread can acquire it multiple times,10| // but there must be a release for every acquire.11|12| public class Mutex implements Semaphore13| {14| private Thread owner = null; // Owner of mutex, null if nobody15| private int lock_count = 0;16|17| private final int _id = Lock_manager.new_id();
  37. 37. 18| public int id() { return _id; }19|20| /**21| * Acquire the mutex. The mutex can be acquired multiple times22| * by the same thread, provided that it is released as many23| * times as it is acquired. The calling thread blocks until24| * it has acquired the mutex. (There is no timeout).25| *26| * @see release27| * @see acquire_without_blocking28| */29|30| public synchronized void acquire( long timeout )31| throws InterruptedException32| { while( acquire_without_blocking() == false)33| this.wait( timeout );34| }35|36| /**37| * Attempts to acquire the mutex. Returns false (and does not38| * block) if it cant get it.39| *40| * @see release41| * @see acquire42| */43|44| public synchronized boolean acquire_without_blocking()45| {46| // Try to get the mutex. Return true if you got it.47|48| if( owner == null )49| { owner = Thread.currentThread();50| lock_count = 1;51| return true;52| }53|54| if( owner == Thread.currentThread() )55| { ++lock_count;56| return true;57| }58|59| return false;60| }61|62| /**63| * Release the mutex. The mutex has to be released as many times64| * as it was acquired to actually unlock the resource. The mutex65| * must be released by the thread that acquired it66| *67| * @throws Mutex.OwnershipException (a RuntimeException) if a thread68| * other than the current owner tries to release the mutex.69| */70|
  38. 38. 71| public synchronized void release()72| {73| if( owner != Thread.currentThread() )74| throw new OwnershipException();75|76| if( --lock_count <= 0 )77| { owner = null;78| notify();79| }80| }81|82| public static class OwnershipException extends RuntimeException83| { public OwnershipException()84| { super("Thread calling release() doesnt own Mutex");85| }86| }87| }Listing 3: Mutex.javaAdmittedly, this preference for the explicit call to acquire() is largely a matter of personal taste.Well look at a few other semaphore implementations next month that are a bit harder tosimulate with a simple synchronized, however.Getting back to Listing 3, the Mutex class starts out with the stuff needed to keep theLock_manager happy. It implements the Semaphore interface with an id() method that returns thevalue of the _id field, which in turn holds a unique value that came from the Lock_manager.There are two versions of the acquire method: acquire() itself and acquire_without_blocking(). Thelatter simply returns false if it cant acquire the mutex. If the mutex isnt owned, it sets owner toreference the thread that called acquire_without_blocking() with the call to Thread.currentThread(),which does the obvious. The blocking version, acquire(), calls the nonblocking version andsuspends itself with a wait() call if it cant get the mutex right off the bat. Note the use of a spinlock here. (I discussed spin locks last month.) The wait() call is inside a loop in case some otherthread breaks in at an inopportune moment and steals the mutex.Interestingly, the Mutex code doesnt actually use the Mutex objects monitor to implement thelock (though it does use the monitor to make sure that two threads dont try to acquire thesame mutex simultaneously -- acquire() and release() are synchronized). A local field called owner isused to decide whether or not to block out an acquiring thread. The owner field references theThread instance that contains the run() method for a given thread. If owner is null, the mutex is upfor grabs, otherwise, some thread "owns" the mutex and any other thread that tries to acquireit will block at the wait() call in acquire().
  39. 39. My Mutex class implements a "recursive" mutex. The "owner" thread can acquire the mutexmore than once, but it must release the mutex by calling release() as many times as it acquired itby calling acquire().This facility is essential when two methods both must acquire the mutex. In the following code,for example, g() calls f(), but f() can also be called from outside -- without going through g() first.If the mutex werent recursive, the thread that called g() would block when g() called f() and triedto acquire the mutex a second time. As it is, the double acquisition isnt a problem since everyacquire() has a corresponding release(). The lock_count field keeps track of the number of times themutex has been locked by its owner.1| class some_class2| { Mutex lock = new Mutex();3|4| public void f()5| { lock.acquire();6| //...7| lock.release();8| }9|10| public void g()11| { lock.acquire();12| //...13| f();14| //...15| lock.release();16| }17| }Until next timeSo thats a simple roll-your-own semaphore. Though its easy enough to use Javas synchronizedstatement directly to do everything the Mutex does, the code is cleaner when you use the Mutexclass, and the associated Lock_manager can solve a class of otherwise hard-to-manage deadlockproblems.Page 5 of 5Next month Ill look at a couple of other useful semaphores: a condition variable and a Djikstracounting semaphore. Ill also discuss critical sections and class-level locks. In future columns,well take a look at other thread-related classes, such as timers and reader/writer locks, andexplore architectural solutions to threading-related problems.About the author
  40. 40. Allen Holub has been working in the computer industry since 1979. He is widely published inmagazines (Dr. Dobbs Journal, Programmers Journal, Byte, MSJ, among others). He has sevenbooks to his credit, and is currently working on an eighth that will present the complete sourcesfor a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now looks atC++ as a bad dream, the memory of which is mercifully fading. Hes been teaching programming(first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University ofCalifornia Berkeley Extension since 1982. Allen offers both public classes and in-house trainingin Java and object-oriented design topics. He also does object-oriented design consulting. Getmore information and contact Allen via his Web site http://www.holub.com.Resources All the real code (the stuff in the com.holub.asynch package) is available in the "Goodies" section on my Web site http://www.holub.com
  41. 41. Programming Java threads in the real world, Part 4Condition variables and counting semaphores -- filling in a few chinks in Javasthreading modelBy Allen Holub, JavaWorld.com, 12/01/98This months column adds a few more classes to the threading arsenal we started building inlast months Java Toolbox column. This time Ill discuss the following: 1. Roll-your-own versions of the condition variable, which replaces wait() and notify() in some situations 2. Djikstras "counting" semaphore, which is used to manage pools of resources(A semaphore is any of several mechanisms used to synchronize and communicate betweenthreads. Think of the "semaphore" as in the flags that two boy scouts use to talk to each otherfrom a distance -- different flag positions represent different letters of the alphabet. Napoleonsarmy used the vanes of windmills on mountain tops to send semaphore messages greatdistances very quickly. The mutex discussed last month, since its a communicationsmechanism, is also a "semaphore.")The condition variable can often be simulated using Java alone -- and Ill show you how -- whilethe counting semaphore cant.Before I start, though, Id like to go over a few loose ends from last month and fix a few bugs.Oops! Could my code have bugs in it?Before leaping into this months meat (now theres a metaphor you can sink your teeth into),lets look at a few loose ends that either caught my eye (or were brought to my attention) afterlast months article went live.During one of my books "peer reviews," an academic reviewer once took exception to thesentence "if youre anything like me, youll forget to ... so you should write your code to do itautomatically." His comment to me was: "I would never admit that in print." This guy was (andas far as I know, still is) a tenured professor at an Ivy League university, and I suppose hiscomment was correct in a literal sense: since he never had written any actual code, he neverhad any bugs to admit. I might as well say it up front, though: my code contains an occasionalbug (gasp). Consequently, I expect an "Oops" section or its moral equivalent to become aregular feature of this column. Theres nothing like having 100,000 people look over your codefor problems to emerge and be highlighted.
  42. 42. Joe Bowbeer pointed out (quite correctly):Why not advise the use of try/finally to prevent an exception from gunking up the works?mutex.acquire();try{ doit();}finally{ mutex.release();}I prefer the [above] form of try/finally because it separates the exceptions that might occur inchanging the state (acquire) from the exceptions that might occur when working in the new state(doit).The other (more intuitive?) form istry{ mutex.acquire(); doit();}finally{ mutex.release();}This requires more programming in release() to ensure that mutex is in a consistent state: ifrelease() is called after an exception in acquire(), the mutex may not have been acquired, or [mayhave been] half-acquired, etc.I should add that Joes last point is important in the case of last months Mutex class. Theacquire_without_blocking() method, where the actual acquisition occurs, doesnt throw anyexceptions at awkward times. The only exception that can be thrown from acquire(), in fact, is anInterruptedException, thrown if a timeout is specified and the waiting thread is interrupted. Thisoperation does not leave the mutex in an unstable state, however.Page 2 of 7Be that as it may, while looking at my code to make sure Joe hadnt found a bug, I found a bugmyself. (Ill show you the code in a moment, but lets discuss the problems in it first.) Theacquire() method was using a standard spin lock while waiting to acquire the mutex, but thisstrategy is appropriate only when the timeout is infinitely large. Ive fixed the problem by
  43. 43. making the loop terminate for timeout values less than Integer.MAX_VALUE (the value I use for"forever"). It continues to use a spin lock in "forever" cases.While I was at it, I also decided to have acquire() indicate whether or not it had timed out. Thechoices here are the usual ones: a return value or an exception toss. I opted for the latterbecause a timeout typically is an error condition, and I didnt want to clutter up the code withunnecessary tests for false return values.I modified the definition for the Semaphore interface to incorporate this new exception:01 | package com.holub.asynch;02 |03 | interface Semaphore04 | {05 | int id ();06 | void acquire(long timeout) throws InterruptedException,07 | Timed_out;08 | void release();09 |10 | public static class Timed_out extends java.lang.RuntimeException11 | { Timed_out(){ super("Timed out while waiting to acquire semaphore"); };12 | }13 | }Note that Semaphore.Timed_out is a RuntimeException, so you dont have to catch it if the timeout isa fatal error (often the case).The new (and this time, I hope, correct) version of acquire() now looks like this:01 | public synchronized void acquire( long timeout ) throws InterruptedException02 | {03 | if( timeout == 0 ) // dont wait at all04 | { acquire_without_blocking();05 | }06 | else if( timeout == Long.MAX_VALUE ) // wait forever07 | { while( !acquire_without_blocking() )08 | this.wait( timeout );09 | }10 | else // wait limited by timeout11 | { if( !acquire_without_blocking() )12 | { this.wait( timeout );13 | if( !acquire_without_blocking() )14 | throw new Semaphore.Timed_out();15 | }16 | }17 | }
  44. 44. Finally, in last months column, I inadvertently used an outdated version of the Lock_managerscomparator class. (It threw a Not_Comparable exception -- an artifact of my own sortimplementation, which I abandoned when Java added an official one.) Anyway, the comparatorclass should look like this:private static class Lock_comparator implements Comparator{ public int compare(Object a, Object b) { return ((Semaphore)a).id() - ((Semaphore)b).id(); } public boolean equals(Object obj) { return obj instanceof Lock_comparator; }}Ive modified the code in the "Goodies" section of my Web site (see Resources) to incorporateall these changes.Now, on to the meat of this months article.Condition variablesForging ahead -- to boldly split infinitives no one has split before:Ive brought up "condition variables" before in the context of wait() and notify(). The centralconcept is that a thread will wait until some condition becomes true. For example, a threadmay need to wait for somebody to push a button before proceeding with an action, or a threadmay wait for something to appear in an empty queue (for the queue-not-empty condition tobecome true).Page 3 of 7Using a condition variable to wait for eventsThe following code illustrates a classic problem that is easily solved with a condition variable:How do I wait for an event to occur without wasting machine cycles in a polling loop? The codesets up a simple TextField and an ActionListener thats notified when the user types the Enter key:01 | class Some_class extends Frame02 | {03 | TextField input = new TextField();04 | String entered = "";05 |06 | public The_wrong_way()07 | { input.addActionListener08 | ( new ActionListener()09 | { public void actionPerformed( ActionEvent e )10 | { entered = input.getText();
  45. 45. 11 | }12 | }13 | );14 |15 | add(input);16 | pack();17 | show();18 | }19 |20 | String read_line(){ return entered; }21 | //...22 | }So far, so good, but lets look at the situation in more detail.When you display the Frame, AWT fires up a thread that monitors events coming in from theoperating system, including key-press events. When the Enter key is pressed, for example, theAWT thread gets the key-press event from the OS and, in turn, calls the listenersactionPerformed() method. The "actionPerformed()" messages are coming in asynchronously fromthe AWT event-loop thread. Put another way: the "actionPerformed()" message is actuallyrunning on that AWT thread.Meanwhile, a user thread (as differentiated from the AWT thread) calls read_line() to find outwhat the user has typed. The problem is that both the AWT and the user thread can access theentered field simultaneously -- a classic race condition. The second thread could call read_line()while the AWT thread is in the middle of ActionPerformed() and end up reading garbage.Solve this first problem with synchronization:01 | class Some_class extends Frame02 | {03 | TextField input = new TextField();04 | String entered = "";05 |06 | public The_wrong_way()07 | { input.addActionListener08 | ( new ActionListener()09 | { public void actionPerformed( ActionEvent e )10 | { synchronized( Some_class.this )11 | { entered = input.getText();12 | }13 | }14 | }15 | );16 |17 | add(input);18 | pack();19 | show();
  46. 46. 20 | }21 |22 | String synchronized read_line(){ return entered; }23 | //...24 | }Note that the inner-class method has to synchronize explicitly on the outer-class object. Simplysynchronizing actionPerformed() doesnt work because youll be synchronizing on the monitor ofthe anonymous inner-class object, and the field you want to guard is in the outer-class object.Moving on, our user thread needs to know when an entire line has been typed to be sure thatread_line() will return a complete line of input, but (and this is the big but), theres no directcommunication between the two threads involved in this transaction. The code running on theAWT thread (actionPerformed()) doesnt tell the user thread that an entire-line-has-been-typedevent has occurred.So how does the caller of read_line() know the string has changed? It could sit in a tight pollingloop calling read_line() and checking the current return value against the previously returnedvalue, but thats an awful lot of machine cycles wasted on doing nothing useful.Page 4 of 7Send in the cavalrySo whats one way for two threads to communicate with each other? (Thats a rhetoricalquestion.) Use a semaphore (think Napoleon, flags, mountain tops). To the rescue comes thesemaphore known as a condition variable. To rehash from previous months material: the basicnotion of a condition variable is that some thread waits (is suspended) on the condition variableuntil the condition it represents becomes true. Every Java object has a condition variableassociated with it -- in the same way it has the mutex used to guard the monitor. You wait forthe condition to become true by calling wait(), and you set the condition to true by callingnotify(). (The notify() call doesnt work in quite this way; Ill talk more about this in a moment.) Itseasy enough to do a roll-your-own condition variable that solves the current thread-communication problem by using wait() and notify().Listing 1 demonstrates how to do this. A condition variable called text_has_been_entered isdeclared up at the top of the class definition. (Were going to wait for the text-has-been-entered condition to become true.) The actionPerformed() method doesnt read the text at all;rather, it simply notifies the condition variable, setting the condition to true. Note that Javarequires you to be in the monitor for an object before you can call wait() or notify() on thatobject, so the synchronized(text_has_been_entered) statement is mandatory. (You must be holdingthe lock associated with the object, by being in either a synchronized function of that objectsclass or a standalone synchronized statement whose argument is the object on which youresynchronizing.)
  47. 47. The synchronized(text_has_been_entered) statement on line 15 is mandatory, since entering thesynchronized block puts us into the monitor of the object referenced by text_has_been_entered.Meanwhile, down in read_line() on line 33, the thread that calls read_line() is waiting for thecondition to become true. When this happens, the new text value is read and returned. Theread_line() method is itself synchronized so that two threads cant attempt to read the same linesimultaneously. Its now possible to have a simple loop like the one in main()while( (input = source.read_line()) != null )System.out.println("Got: " + input );which blocks until a new input line arrives. Listing 1: Using wait() and notify() for a condition variable01 | import java.awt.*;02 | import java.awt.event.*;03 |04 | public class Input_source extends Frame05 | {06 | int[] text_has_been_entered = new int[1]; // The condition variable07 |08 | TextField input = new TextField();09 |10 | public Input_source()11 | {12 | input.addActionListener13 | ( new ActionListener()14 | { public void actionPerformed( ActionEvent e )15 | { synchronized( text_has_been_entered )16 | { text_has_been_entered.notify(); // set the condition true17 | }18 | }19 | }20 | );21 |22 | add(input);23 | pack();24 | show();25 | }26 |27 | /** A blocking function that works like readLine(), but gets its28 | * text from the current windows text area. The function doesnt29 | * return until somebody types a line of text, whereupon it returns30 | * the line. Returns null if the user types an empty line.

×