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
findMe(), to find the element in the linked list corresponding to the current thread
firstWriterIndex(), to find the very first writer
lastGrantedIndex(), to find the very last element that has a granted lock
We will see the use of those three methods when we implement the forReading(), forWriting(), upgrade(), downgrade(), and release() methods. That linked list serves as the queue in which threads waiting to obtain a lock are placed. Each element in that
linked list is of type WaitingListElement. Each element is created by a thread. That thread's ID is the key to its corresponding element. I have overridden the equals() method in the WaitingListElement class to define two elements as equal if their keys are equal.
forReading() and forWriting() methods delegate their no argument versions to their argument taking siblings. (That was too easy; I like that delegation
stuff.)
Let's look at the forReading() (the argument-taking version) method implementation. That method tries to find the element corresponding to the thread on
which the call is being made. If an element is not found, forReading() creates a new one and adds it to the waiting list. If an element is found, that indicates that the thread already owns a
lock. It does not matter whether the lock owned is read or write, since the write lock includes read as part of it. Therefore,
the lock count increases and the method returns. If the read lock cannot be granted, the thread waits if instructed to do
so, as indicated by the wait-time parameter passed into the method call. Each time the thread wakes up, either because it
timed out or because it was notified to wake up, it checks the condition to see if the read lock can be granted. That is referred
to as the double-checked locking pattern. (see Chapter 20, "Double-Checked Locking" by Doug Schmidt and Tim Harrison in Pattern Languages of Program Design 3 [Software Patterns Series], Robert C. Martin, Dirk Riehle, Frank Buschmann, and John Vlissides in Resources for a discussion of that pattern.) Double-checked locking prevents erroneous locking conditions. Finally, if the lock cannot
be obtained due to a time-out, a false value is returned to the method caller. Before returning that false value, the method
cleans up the waiting list by removing the element corresponding to the calling thread from the list. We also notify all waiting
threads so that they can be granted locks if possible.
Now, let's look at the forWriting() (again, the argument-taking version) method implementation. It performs similar functions as the forReading() implementation, with a few exceptions. A thread is granted a write lock only when the element corresponding to that thread
is the first in line, that is, there are no other threads with locks. In that case, when searching the waiting list for an
element corresponding to that thread, the lock type matters. There are two cases to consider. If the lock type is for writing,
the method simply returns with a true value. If the lock type is for reading, an attempt is made to upgrade the lock. If that
attempt succeeds, the lock count increases, and the method returns a true value to the caller. That process may fail either
because upgrades are not allowed by the implementation or because of a timeout. If either occurs, the method returns a false
value.