The complete code for all examples is available on the Web (see Code Examples).
A few notes about the program. The function findPerson(name) is to be used by both the
friends and enemies threads; hence, it will return a pointer to the previous element of the people
list (the liquidate function needs access to the previous person to remove a person from the list).
The appropriate element of the list must remain locked when findPerson() returns, and which
lock is appropriate will change with the different designs. It is possible to search for someone who
has been liquidated, so null is a possible return value. We'll have to be careful.
Single, Global Mutex
Single, global mutex is by far the simplest design (Code Example 12-3). All that is necessary is to
lock the mutex before starting a search and release it after the thread is finished with liquidation or
giving raises (Figure 12-5). This is the extreme case of coarse grain locking. It has very little
overhead and has the best performance when there is only one thread or when the delay times are
zero. Once the delay times go up and more threads are added, the wall-clock performance of this
design goes to pot. It will not get any advantage from using multiple CPUs either.
Figure 12-5. Friends/Enemies: Global Mutex Lock
There are a couple of things worth noting. The mutex protects the entire list--every element on it,
all the pointers, and the data inside (name and salary). It is not legal for a thread to use a pointer to
any element of the list if it does not hold the mutex.
One other thing that you may notice if you run this code is an odd tendency for one thread to get
the mutex and then keep it. Typically, one thread will get the lock and execute a dozen or more
iterations of its loop before another thread ever runs its loop at all. Often, one thread will run to
completion before any other thread even starts! Why? Because there is no work being done
outside the synchronized loop and as soon as the running thread releases the synchronized section,
the very next thing it does is reacquire it. A call to Thread.yield() in the code forces it to
behave more the way we'd expect. In a "real" program, this would not be an issue because it would
be doing real work outside the loop.
In Code Example 12-3 we see the central function that runs down a list of friends, looking them
up and giving them raises. It locks the mutex, does all its work, then unlocks the mutex. It gets the
next friend off the list of friends and starts all over again. There are no more than a few dozen
instructions between the time it unlocks the mutex and locks it again! The probability of another
Search WWH :