March 5, 2024

ReentrantReadWriteLock in Java With Examples

In this post we’ll see the usage of java.util.concurrent.locks.ReadWriteLock interface and its implementing class ReentrantReadWriteLock in Java with examples.

ReadWriteLock in Java Concurrency

ReadWriteLock as the name itself suggests has a pair of associated locks-

  • One for read-only operations
  • One for writing operations

The usage of read lock and write lock is as follows-

  • The read lock may be held simultaneously by multiple reader threads as long as there are no threads with write lock access.
  • The write lock is exclusive. Which means no thread has acquired either read lock or write lock when the thread obtains a write lock.

Benefits of ReadWriteLock

The traditional way of synchronizing the threads requires a mutually exclusive lock. Even if thread is just reading the shared resource (not modifying it) lock is still mutually exclusive i.e. no other thread can enter the critical section while the resource is locked.

A read-write lock allows for a greater level of concurrency in accessing shared data than that permitted by a mutual exclusion lock. It works on a principle that while only a single thread at a time (a writer thread) can modify the shared data, in many cases any number of threads can concurrently read the data (hence reader threads) which may help in increasing performance in a multi-threaded environment.

ReentrantReadWriteLock in Java Concurrency

ReentrantReadWriteLock class in Java is an implementing class of the ReadWriteLock interface. It is used in the following way.

To create a ReentrantReadWriteLock-

ReadWriteLock rwl = new ReentrantReadWriteLock();

For getting read lock-

Lock readLock = rwl.readLock();

For getting write lock-

Lock writeLock = rwl.writeLock();

Here note that ReadLock and WriteLock are the static nested classes with in the ReentrantReadWriteLock class-

  • ReentrantReadWriteLock.ReadLock- The lock returned by method ReadWriteLock.readLock().
  • ReentrantReadWriteLock.WriteLock- The lock returned by method ReadWriteLock.writeLock().

Locking and unlocking using read lock and write lock is done as follows.

Read lock-
rwl.readLock().lock();
try {
  ..
  ..
}finally{
  rwl.readLock().unlock();
}
Write Lock-
rwl.writeLock().lock();
try {
  ..
  ..
}finally{
  rwl.writeLock().lock();
}

As you can see ReentrantReadWriteLock follows the same convention as followed with ReentrantLock in Java where call to lock() method is placed before try block and then followed with a try-finally or try-catch-finally block and uses finally block to call unlock() method.

That way unlock() method is called only if the lock is actually acquired and it is also ensured that the unlock() method is called if there is any error after the lock is acquired.

Java ReentrantReadWriteLock constructors

  • ReentrantReadWriteLock()- Creates a new ReentrantReadWriteLock with default (nonfair) ordering properties.
  • ReentrantReadWriteLock(boolean fair)- Creates a new ReentrantReadWriteLock with the given fairness policy.

ReentrantReadWriteLock example in Java

In the example we’ll have a HashMap that is used by multiple threads. While putting element in the HashMap a write lock is acquired as it is a modifying operation. In case of get method a read lock is used so several threads can get values from the HashMap. Then two write threads and three read threads are started to put and get values from the HashMap.

public class RWLDemo {
  private final Map<String, String> numberMap = new HashMap<String, String>();
  private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  // get method with read lock
  public String get(String key) {
    System.out.println("Waiting to acquire lock in get method...");
    rwl.readLock().lock();
    System.out.println("Acquired read lock in get method");
    try { 
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
       }
       return numberMap.get(key); 
    }
    finally { 
      System.out.println("releasing read lock in get method ");
      rwl.readLock().unlock(); 
    }
  }
   
  // Put method with write lock
  public String put(String key, String value) {
    System.out.println("Waiting to acquire lock in put method...");
    rwl.writeLock().lock();
    System.out.println("Acquired write lock in put method");
    try { 
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      return numberMap.put(key, value); 
    }
    finally {
      System.out.println("Releasing write lock in put method ");
      rwl.writeLock().unlock(); 		  
    }
  }
   
  public static void main(String[] args) {
    RWLDemo rwlDemo = new RWLDemo();
    // To put some initial values in the Map
    rwlDemo.initialValueInMap();
    // Starting Three read threads and two write threads
    Thread wThread1 = new Thread(new WriterThread(rwlDemo, "3", "Three"));
    Thread rThread1 = new Thread(new ReadThread(rwlDemo, "1"));        
    Thread rThread2 = new Thread(new ReadThread(rwlDemo, "1"));
    Thread wThread2 = new Thread(new WriterThread(rwlDemo, "4", "Four"));
    Thread rThread3 = new Thread(new ReadThread(rwlDemo, "2"));

    wThread1.start();
    rThread1.start();
    rThread2.start();
    rThread3.start();
    wThread2.start();
  }

  private void initialValueInMap(){
    // Putting some values in the map
    numberMap.put("1", "One");
    numberMap.put("2", "Two");
  }
}

class ReadThread implements Runnable {
  RWLDemo rwDemo;
  String key;
  ReadThread(RWLDemo rwDemo, String key){
    this.rwDemo = rwDemo;
    this.key = key;
  }
  public void run() {
    System.out.println("Value - " + rwDemo.get(key));
  }
}

class WriterThread implements Runnable {
  RWLDemo rwDemo;
  String key;
  String value;
  WriterThread(RWLDemo rwDemo, String key, String value){
    this.rwDemo = rwDemo;
    this.key = key;
    this.value = value;
  }
  public void run() {
    rwDemo.put(key, value);
  }
}
Output
Waiting to acquire lock in put method...
Waiting to acquire lock in put method...
Waiting to acquire lock in get method...
Waiting to acquire lock in get method...
Acquired read lock in get method
Waiting to acquire lock in get method...
Acquired read lock in get method
releasing read lock in get method 
Value - Two
releasing read lock in get method 
Acquired write lock in put method
Value - One
Releasing write lock in put method 
Acquired read lock in get method
releasing read lock in get method 
Acquired write lock in put method
Value - One
Releasing write lock in put method 

You can see from the output initially two read threads are acquired both can access the locked section. Once read locks are released then only write lock is acquired as write lock has to get exclusive access. There is another read lock that waits for the write lock to release the write lock then only read lock is acquired.

ReentrantReadWriteLock Properties

  1. There is no reader or writer preference ordering for lock access. However, it does support an optional fairness policy.
  2. When ReentrantReadWriteLock is constructed as non-fair, which is the default, the order of entry to the read and write lock is unspecified.
  3. When ReentrantReadWriteLock is constructed as fair, threads contend for entry using an approximately arrival-order policy. When the currently held lock is released, either the longest-waiting single writer thread will be assigned the write lock, or if there is a group of reader threads waiting longer than all waiting writer threads, that group will be assigned the read lock.
  4. Both read and write lock can reacquire read or write locks in the style of a ReentrantLock. See example here.
  5. Reentrancy also allows downgrading from the write lock to a read lock, by acquiring the write lock, then the read lock and then releasing the write lock.
  6. Upgrading from a read lock to the write lock is not possible.
Reference: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/locks/ReentrantReadWriteLock.html

That's all for the topic ReentrantReadWriteLock in Java With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment