Programming Java threads in the real world, Part 3

Roll-your-own mutexes and centralized lock management

1 2 Page 2
Page 2 of 2

A manageable mutex

Now let's move on and look at the other side of the equation: a class that implements a semaphore. 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 earlier) really depends on the situation. The main advantage is in being able to use the Lock_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_class
2| {   
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_class
2| {
3|     private Object lock = new int[1];
4| 
5|     public void f()
6|     {   synchronized( lock )
7|         {
8|             //...
9|         }
10|     }
11| }

even though it's 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 by
9| // 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 Semaphore
13| {
14|     private Thread  owner      = null;  // Owner of mutex, null if nobody
15|     private int     lock_count = 0;
16| 
17|     private final int _id   = Lock_manager.new_id();
18|     public        int id()  {   return _id;         }
19| 
20|     /**
21|    * Acquire the mutex. The mutex can be acquired multiple times
22|    * by the same thread, provided that it is released as many
23|    * times as it is acquired. The calling thread blocks until
24|    * it has acquired the mutex. (There is no timeout).
25|    *
26|    * @see release
27|    * @see acquire_without_blocking
28|    */
29| 
30|     public synchronized void acquire( long timeout )
31|                                         throws InterruptedException
32|     {   while( acquire_without_blocking() == false)
33|             this.wait( timeout );
34|     }
35| 
36|     /**
37|    * Attempts to acquire the mutex. Returns false (and does not
38|    * block) if it can't get it.
39|    *
40|    * @see release
41|    * @see acquire
42|    */
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 times
64|    * as it was acquired to actually unlock the resource. The mutex
65|    * must be released by the thread that acquired it
66|    *
67|    * @throws Mutex.OwnershipException (a RuntimeException) if a thread
68|    *      other than the current owner tries to release the mutex.
69|    */
70| 
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 RuntimeException
83|     {   public OwnershipException()
84|         {   super("Thread calling release() doesn't own Mutex");
85|         }
86|     }
87| }

Listing 3: Mutex.java

Admittedly, this preference for the explicit call to acquire() is largely a matter of personal taste. We'll look at a few other semaphore implementations next month that are a bit harder to simulate with a simple synchronized, however.

Getting back to Listing 3, the Mutex class starts out with the stuff needed to keep the Lock_manager happy. It implements the Semaphore interface with an id() method that returns the value 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(). The latter simply returns false if it can't acquire the mutex. If the mutex isn't owned, it sets owner to reference 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 and suspends itself with a wait() call if it can't get the mutex right off the bat. Note the use of a spin lock here. (I discussed spin locks last month.) The wait() call is inside a loop in case some other thread breaks in at an inopportune moment and steals the mutex.

Interestingly, the Mutex code doesn't actually use the Mutex object's monitor to implement the lock (though it does use the monitor to make sure that two threads don't try to acquire the same mutex simultaneously -- acquire() and release() are synchronized). A local field called owner is used to decide whether or not to block out an acquiring thread. The owner field references the Thread instance that contains the run() method for a given thread. If owner is null, the mutex is up for grabs, otherwise, some thread "owns" the mutex and any other thread that tries to acquire it will block at the wait() call in acquire().

My Mutex class implements a "recursive" mutex. The "owner" thread can acquire the mutex more than once, but it must release the mutex by calling release() as many times as it acquired it by 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 weren't recursive, the thread that called g() would block when g() called f() and tried to acquire the mutex a second time. As it is, the double acquisition isn't a problem since every acquire() has a corresponding release(). The lock_count field keeps track of the number of times the mutex has been locked by its owner.

1| class some_class
2| {   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 time

So that's a simple roll-your-own semaphore. Though it's easy enough to use Java's synchronized statement directly to do everything the Mutex does, the code is cleaner when you use the Mutex class, and the associated Lock_manager can solve a class of otherwise hard-to-manage deadlock problems.

Next month I'll look at a couple of other useful semaphores: a condition variable and a Djikstra counting semaphore. I'll also discuss critical sections and class-level locks. In future columns, we'll take a look at other thread-related classes, such as timers and reader/writer locks, and explore architectural solutions to threading-related problems.

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

Learn more about this topic

  • 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
1 2 Page 2
Page 2 of 2