Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
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
Page 3 of 5
Listing 1 shows the core interface I use for all my roll-your-own semaphore classes: the Semaphore. It's in the com.holub.asynch package, as are all the thread-related classes and interfaces I'll be discussing. (I've 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 Semaphore
4| {
5| int id ();
6| void acquire(long timeout) throws InterruptedException;
7| void release();
8| }
Listing 1: Semaphore.java
If you haven't worked much with semaphores, a semaphore has to have two properties in order to be useful:
id() request. The current implementation uses a unique integer for the purpose.Any semaphore that implements Semaphore can be locked in groups using the Lock_manager class shown in Listing 2. There are several things going on here. First of all, I've 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 is another example. In fact, the Lock_manager itself is a utility, since it's made up solely of static methods.
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_manager
08| {
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 it's created.
22| * This function returns once all of the locks in the incoming
23| * array have been successfully acquired. Locks are always
24| * acquired in ascending order of ID to attempt to avoid
25| * deadlock situations.
26| *
27| * @param locks All of these locks must be acquired before
28| * acquire_multiple returns.
29| * @param timeout Maximum time to wait to acquire each
30| * lock (milliseconds). The total time for the multiple
31| * acquire operation could be (timeout * locks.length).
32| **/
33|
34| public static void acquire_multiple( Semaphore[] locks, long timeout )
35| throws InterruptedException
36| { try
37| {
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| // shouldn't happen. Convert it to an uncaught
45| // exception to terminate the program.
46|
47| throw new Error( e.toString() );
48| }
49| }
50|
51| private static class Lock_comparator implements Comparator
52| { public int compare(Object a, Object b)
53| throws Comparable.Not_comparable
54| { 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 get around design problems in
the language itself or in existing libraries. The problem in Java is that neither arrays nor primitive types really are true
objects. If they were, they would contain all the methods needed to manipulate them. For example, you would be able to find
a cosine using:
int x = PI; x.cosine();
You'd also be able to extend int, and so forth. In the case of arrays, you ought to be able to sort them by asking them to sort themselves, like this:
int array[] = new int[]; //... array.sort();
Unfortunately, Java isn't a "pure" object-oriented language in which everything is an object, so we 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 );
The problem is complicated a bit by arrays of objects of some arbitrary class. How can a generic sort utility figure out the
correct ordering? The Object class contains an equals() function, but we'd need a greater_than() as well to do sorting.
The Strategy pattern
To the rescue comes the "Strategy" pattern. The notion is to pass into a method or object another object that encapsulates
the strategy needed to do something. There are lots of examples of Strategy in Java. A java.awt.LayoutManager, for example, encapsulates the strategy that a Frame uses for laying itself out. You can change the layout simply by changing this strategy. You don't have to define a new kind
of Frame class using derivation or some other mechanism. This, in turn, makes Frame much more "reusable" since the behavior of a Frame can be 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, the Lock_comparator (at the bottom of the listing) implements the comparison strategy by implementing the new (to JDK 1.2) java.util.Comparator interface and its compare() method. (This works like C's strcmp(): it returns a negative number, zero, or a positive number depending on whether a is less than, equal to, or greater than b.) An instance of the Comparator is passed to the Arrays.sort() method like this:
Arrays.sort(locks, new Lock_comparator() );
The Lock_comparator object's compare() method is called by Arrays.sort() when it needs to compare objects. C programmers will recognize this approach as very similar to that of qsort(), which is passed a pointer to a compare method -- another example of Strategy.
How Lock_manager works
Now, let's look at what the Lock_manager actually does. It starts out with the static new_id() method that returns a unique int every time it's called. This method will be called from the various objects that implement the Semaphore interface to get the value they'll use for their ID.
The acquire multiple method is used to safely acquire groups of semaphores. You pass it an array of objects that implement Semaphore, it sorts the array by ID, and then acquires the semaphores one at a time in ID order. Consistent acquisition of multiple
semaphores in ID order effectively eliminates the deadlock scenario we've been discussing.