Reentrant Read-Write Lock

The explicit lock implemented by the ReentrantLock class is mutually exclusive—that is, only one thread at a time can obtain the lock and thereby access the shared data. There are cases where shared data is read more often than it is modified—that is, there are more readers (or reader threads) than writers (or writer threads) accessing the shared data. Taking into consideration such behavior to improve the level of concurrency would be a challenge using a mutually exclusive lock, as the lock acquisition is exclusive, irrespective of whether it is a read or a write operation.

The ReadWriteLock interface provides a more flexible and sophisticated locking mechanism than a mutually exclusive lock for accessing shared data. Figure 23.3 shows that the ReentrantReadWriteLock class implements the ReadWriteLock interface. It implements a read-write lock that actually maintains a pair of associated locks, one for read operations and one for write operations. The read lock is an instance of the ReadLock inner class and the write lock is an instance of the WriteLock inner class, both of which implement the Lock interface. The ReadWriteLock interface defines the methods to obtain the read and the write lock of a read-write lock.

The following methods are defined in the ReadWriteLock interface:

Lock readLock()
Lock writeLock()

These methods are implemented by inner classes in the ReentrantReadWriteLock class:

Click here to view code image

ReentrantReadWriteLock.ReadLock readLock()
ReentrantReadWriteLock.WriteLock writeLock()

Return the lock used for reading and for writing, respectively. The inner classes ReadLock and WriteLock implement the Lock interface.

The ReentrantReadWriteLock class provides the following constructors to create read-write locks with or without a fairness policy:

ReentrantReadWriteLock()

Creates an instance of ReentrantLock with non-fair ordering policy—imposing no ordering for lock access. A non-fair lock may be acquired by a thread at the expense of other waiting reader and writer threads.

Click here to view code image

ReentrantReadWriteLock(boolean fair)

Creates an instance of ReentrantLock with the given fairness policy. The value true indicates to use a fair ordering policy.

The basic idea behind a read-write lock is that a thread can use its read and write locks to perform thread-safe read and write operations on shared data, respectively. A thread is bound by the following lock acquisition rules for the read lock and the write lock of a read-write lock:

  • The read lock: Only if no thread holds the write lock can a thread acquire the read lock. This means that the read lock can be shared by several reader threads— that is, a group of reader threads can access the shared data concurrently, as long as the write lock is not held by any thread.
  • The write lock: Only if no thread holds the write lock or the read lock can a thread acquire the write lock. The write lock is thus mutually exclusive for writer threads—only one writer thread at a time can access the shared data.

Since the inner classes ReadLock and WriteLock implement the Lock interface, the lock acquisition policies of the Lock interface discussed earlier (p. 1462) are applicable to the read lock and the write lock, bearing in mind the specialized lock acquisition rules mentioned above for the read-write lock:

  • Unconditional locking using the blocking lock() method
  • Polled locking using the non-blocking tryLock() method
  • Timed locking using the tryLock(timeout, timeunit) method
  • Interruptible locking using the lockInterruptibly() method

As with any implementation of the Lock interface, the read lock and the write lock of a read-write lock must be released when done:

  • Released using the unlock() method

Example 23.13 Reentrant Read-Write Lock Counter

Click here to view code image

package safe;
import java.util.OptionalInt;
import java.util.concurrent.locks.*;
public class ReentrantRWLockCounter implements ICounter {
  private ReadWriteLock rwl = new ReentrantReadWriteLock();           // (1)
  private Lock readLock = rwl.readLock();                             // (2)
  private Lock writeLock = rwl.writeLock();                           // (3)
  private int counter = 0;
  @Override
  public int getValue() {                                             // (4)
    readLock.lock();
    try {
//      System.out.println(Thread.currentThread().getName() + “: ” + counter);
      return counter;
    } finally { readLock.unlock(); }
  }
  @Override
  public void increment() {                                           // (5)
    writeLock.lock();
    try {
      counter++;
    } finally { writeLock.unlock(); }
  }
  public int incrementAndGet() {                                      // (6)
    writeLock.lock();                 // Acquire write lock.
    try {
      return ++counter;                                               // (7)
//    ++counter;                      // Increment.                   // (8)
//    return getValue();              // Get the new value.           // (9)
    } finally { writeLock.unlock(); } // Release write lock.
  }
  public int getAndIncrement() {                                      // (10)
    writeLock.lock();                 // Acquire write lock.
    try {
      return counter++;               // Get and increment.           // (11)
    } finally { writeLock.unlock(); } // Release write lock.
  }
  public boolean incrIfPossible() {                                   // (12)
    if (writeLock.tryLock()) {          // Attempts to acquire the write lock.
      try {                             // Write lock acquired.
        counter++;
        return true;
      } finally { writeLock.unlock(); } // Write lock released.
    } else {                            // Write lock not acquired.
      return false;
    }
  }
  public OptionalInt getIfPossible() {                                // (13)
    if (readLock.tryLock()) {           // Attempts to acquire the read lock.
      try { return OptionalInt.of(counter); }
      finally { readLock.unlock(); }    // Read lock released.
    } else {                            // Read lock not acquired.
      return OptionalInt.empty();
    }
  }
}

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

Click here to view code image

Reentrant Read-Write Lock Counter: 10000