Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Programming Java threads in the real world, Part 3

Roll-your-own mutexes and centralized lock management

  • Print
  • Feedback

Page 2 of 5

So, roll your own

You can implement the two-lock strategy by synchronizing on something other than the containing object. Rather than synchronizing the methods, you can define two local variables to use as locks and synchronize on them:

1| class Complicated_and_thread_safe
2| {
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 b
10| 
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 y
16| 
17|     public void use_x()      { synchronized(xy_lock){
 /*...*/ } }
18|     public void use_y()      { synchronized(xy_lock){
/*...*/ } }
19|     public void use_x_and_y(){ synchronized(xy_lock){
/*...*/ } }
20| 
21|     // partition 3, functions use a, b, x, and y
22| 
23|     public void use_everything()
24|     {   synchronized(ab_lock)       // grab both locks
25|         {   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 haven't synchronized the methods themselves in this example. (Remember, synchronizing a method is effectively the same thing as wrapping all the code in the method in a synchronized(this){...} block.) Instead, I'm providing a unique lock for each partition (ab_lock and xy_lock) and then explicitly synchronizing on these individual locks.

Java associates locks with objects (instance of some class that extends Object, so I can't use primitive-type variables as locks here. I don't want to spend unnecessary time calling constructors 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 don't believe me, compile the following code:

1| public class foo
2| {   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 can't have everything). I've used a one-element array for my lock, rather than something like an Integer, because arrays come into existence very efficiently. For example, there's no constructor call.

In the foregoing example, it's critical that methods that acquire both locks always acquire them in the same order, otherwise we end up in the Wilma-and-Betty deadlock scenario discussed last month. Acquiring multiple locks is a commonplace enough problem that some operating systems have system calls for this purpose. It would be nice to have an easy way to acquire multiple locks, in Java, without having to worry about the order-of-acquisition problem. The remainder of this month's column describes one way to do that.

  • Print
  • Feedback

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