← Back to Multithreading Course

Class 35: ReadWriteLock and Read-Write Operations

Part 6 — Locks and Concurrent Collections

In the previous class, we learned about ReentrantLock. It allows us to explicitly acquire and release locks while protecting shared data.

But let's consider a different situation.

Imagine that an application has a shared configuration object. Many threads need to read its values, but only a few threads occasionally update them.

Should every reader have to wait for other readers to finish?

Not necessarily. Reading data does not usually change it. Multiple threads can safely read the same data at the same time, provided no thread is modifying it.

Java provides a locking mechanism designed for this situation: ReadWriteLock.

1. Understanding the Problem

Let's first understand a simple example.

Imagine an online shopping application where thousands of users can view product details.

Occasionally, an administrator updates a product's price.

We have two types of operations:

Multiple users should be able to read the product details simultaneously.

However, when someone updates the product details, other readers and writers should wait until the update is complete.

This is exactly the kind of situation where a ReadWriteLock can be useful.

2. What Is ReadWriteLock?

ReadWriteLock is an interface available in the java.util.concurrent.locks package.

It provides two separate locks:

Let's understand the rules.

Situation Allowed?
Multiple threads reading together Yes
One thread writing while another reads No
Multiple threads writing together No
One thread reading while another reads Yes

The important idea is that reading can happen concurrently, but writing requires exclusive access.

3. Creating a ReadWriteLock

Java provides an implementation called ReentrantReadWriteLock.

We can create it like this:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

ReadWriteLock lock = new ReentrantReadWriteLock();

Notice that we are declaring the variable using the ReadWriteLock interface and creating an object of ReentrantReadWriteLock.

This is similar to declaring a List reference and creating an ArrayList object.

Now we can access the two locks:

lock.readLock();

lock.writeLock();

The first gives us the read lock, and the second gives us the write lock.

4. Using the Read Lock

Let's start with a simple example.

We will create a shared resource containing a value. Multiple threads will read that value.

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class SharedData {

    private int value = 100;

    private final ReadWriteLock lock =
            new ReentrantReadWriteLock();

    public int readValue() {

        lock.readLock().lock();

        try {

            System.out.println(
                Thread.currentThread().getName() + " is reading"
            );

            return value;

        } finally {

            lock.readLock().unlock();
        }
    }
}

public class ReadLockExample {

    public static void main(String[] args) {

        SharedData data = new SharedData();

        Runnable task = () -> {

            int result = data.readValue();

            System.out.println(
                Thread.currentThread().getName()
                + " read value: " + result
            );
        };

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");
        Thread thread3 = new Thread(task, "Thread 3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

All three threads use the same SharedData object.

Each thread acquires the read lock before accessing the shared value.

Because multiple threads can hold the read lock together, they do not need to wait for one another.

The exact order of the output may vary.

5. Using the Write Lock

Now let's see how we update the shared data.

We will add a method that changes the value.

public void updateValue(int newValue) {

    lock.writeLock().lock();

    try {

        System.out.println(
            Thread.currentThread().getName() + " is updating"
        );

        value = newValue;

    } finally {

        lock.writeLock().unlock();
    }
}

Notice that we are using:

lock.writeLock().lock();

A thread must acquire the write lock before modifying the shared data.

While a thread holds the write lock:

Once the update is complete, the thread releases the write lock.

6. Complete Example: Reading and Updating Shared Data

Let's combine both operations into one practical example.

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class SharedData {

    private int value = 100;

    private final ReadWriteLock lock =
            new ReentrantReadWriteLock();

    public int readValue() {

        lock.readLock().lock();

        try {

            System.out.println(
                Thread.currentThread().getName() + " is reading"
            );

            return value;

        } finally {

            lock.readLock().unlock();
        }
    }

    public void updateValue(int newValue) {

        lock.writeLock().lock();

        try {

            System.out.println(
                Thread.currentThread().getName() + " is updating"
            );

            value = newValue;

            System.out.println("Updated value: " + value);

        } finally {

            lock.writeLock().unlock();
        }
    }
}

public class ReadWriteLockExample {

    public static void main(String[] args) {

        SharedData data = new SharedData();

        Thread reader1 = new Thread(() -> {
            System.out.println("Reader 1: " + data.readValue());
        });

        Thread reader2 = new Thread(() -> {
            System.out.println("Reader 2: " + data.readValue());
        });

        Thread writer = new Thread(() -> {
            data.updateValue(500);
        });

        reader1.start();
        reader2.start();
        writer.start();
    }
}

All three threads share the same SharedData object.

The two readers can read at the same time. The writer needs exclusive access before updating the value.

Depending on which thread acquires its lock first, the writer may update before or after the readers.

What matters is that the locks prevent a reader from accessing the data while a writer is modifying it.

7. Why Do We Need finally Here Too?

Just like ReentrantLock, ReentrantReadWriteLock requires us to release locks explicitly.

Consider this code:

lock.writeLock().lock();

try {

    value = 500;

} finally {

    lock.writeLock().unlock();
}

If an exception occurs while updating the value, the finally block still executes.

This ensures that the write lock is released.

Always follow this pattern when using explicit locks.

8. ReadWriteLock vs. ReentrantLock

Let's compare the two locking mechanisms.

Feature ReentrantLock ReadWriteLock
Lock types One exclusive lock Separate read and write locks
Multiple readers Cannot hold the same lock together Can read together
Writing Exclusive access Exclusive access
Useful when Access needs general protection Reads are frequent and writes are less frequent

If most operations only read shared data and updates happen occasionally, ReadWriteLock may allow better concurrency.

However, it is not automatically faster in every situation. The benefit depends on the workload and the cost of acquiring and managing locks.

9. When Should We Use ReadWriteLock?

Let's consider a few practical examples.

The key idea is to use ReadWriteLock when concurrent reading is useful and updates need exclusive access.

If a resource is updated very frequently, or the protected operations are very small, a simple lock may be a better choice.

Test Your Understanding

Let's see how well you understood this class!

Question 1: What does ReadWriteLock provide?

Question 2: Can multiple threads hold the read lock at the same time?

Question 3: What happens while a thread holds the write lock?

Question 4: Which implementation commonly provides ReadWriteLock in Java?

Question 5: In which situation might ReadWriteLock be useful?

Class Summary

Let's quickly revise what we learned today.

  • ReadWriteLock provides separate read and write locks.
  • Multiple threads can hold the read lock simultaneously.
  • The write lock provides exclusive access.
  • ReentrantReadWriteLock is a standard implementation.
  • Use readLock() to acquire the read lock.
  • Use writeLock() to acquire the write lock.
  • Always release acquired locks in a finally block.
  • ReadWriteLock can be useful when reading is frequent and writing is less frequent.

Remember: Multiple readers can work together, but a writer needs exclusive access to the shared resource.

Next Class: Thread-Safe Collections in Java

So far, we have learned how to protect shared variables and shared resources using locks.

But what happens when multiple threads need to work with the same ArrayList, HashMap, or another collection?

In the next class, we will understand thread-safe collections in Java and explore how they help us work with shared data safely.

Let's keep learning, one concept at a time! See you in Class 36.

0% Completed

You have completed 35 out of 46 classes.