Example 23.12 illustrates using a reentrant lock to implement a thread-safe counter. The class ReentrantLockCounter at (1) implements the ICounter interface (Example 23.7). It defines a counter whose value can be read by the method get-Value() at (1), and incremented by the method increment() at (5). The class instantiates a ReentrantLock instance at (2) that is used in the two methods to implement exclusive access to the counter field.

The getValue() method at (4) acquires the rl lock, reads and stores the current counter value locally, unlocks the rl lock, and returns the locally stored counter value. The increment() method at (5) acquires the rl lock, increments the counter, calls the getValue() method (ignoring the returned value), and releases the rl lock. The setup with the locks ensures that any read or write operation will have exclusive access to the counter field. When testing the ReentrantLockCounter class with Example 23.7, p. 1451, the output shows that the counter was incremented correctly by the threads.

Note that the expression of a return statement in a try block is evaluated first and its value stored locally, before the finally block is executed. The stored result is returned after the completion of the finally block.

Intrinsic locks are reentrant: A thread that has acquired an intrinsic lock can acquire the same intrinsic lock again immediately (§22.4, p. 1390). This is also true of the locks implemented by the ReentrantLock class. In other words, explicit locks implemented by the ReentrantLock class are reentrant, as their name implies. The following code relies on the lock being reentrant:

Click here to view code image

frl.lock();
frl.lock();
// Access the resource. Lock hold count is 2.
frl.unlock();
frl.unlock();

Example 23.12 also illustrates the reentrant nature of explicit locks. The increment() method of the ReentrantLockCounter class calls the method getValue(). The current thread already holds the rl lock when the getValue() method is called. The get-Value() method also calls the lock() method on the rl lock. If rl was not reentrant, the current thread would not be able to proceed in the getValue() method and would starve waiting for the lock to become available, as it already has been acquired by the current thread. The reentrant nature of the rl lock allows the current thread to reacquire the lock and execute the getValue() method.

Example 23.12 Reentrant Lock Counter

Click here to view code image

package safe;
import java.util.concurrent.locks.*;
public class ReentrantLockCounter implements ICounter {               // (1)
  private Lock rl = new ReentrantLock();                              // (2)
  private int counter = 0;                                            // (3)
  @Override
  public int getValue() {                                             // (4)
    rl.lock();
    try {
      return counter;
    } finally { rl.unlock(); }
  }
  @Override
  public void increment() {                                           // (5)
    rl.lock();
    try {
      counter++;                                                      // (6)
      getValue();                                                     // (7)
    } finally { rl.unlock(); }
  }
}

Output from the program in Example 23.7, p. 1451:

Click here to view code image Reentrant Lock Counter:            10000