Use case: situations where reads are far more common than writes (config data, cache, wikipedia).

11.1 Reader Writer Lock

Reader/Writer Lock: A specialized lock abstract data type designed for this scenario. It permits:

  • Multiple threads to hold the lock simultaneously for reading.
  • OR, exactly one thread to hold the lock for writing.

Invariant: writers == 0 || readers == 0 (cannot have readers and writers concurrently) AND writers <= 1.

In Java:

  • synchronized does not provide RW lock semantics.
  • Use java.util.concurrent.locks.ReentrantReadWriteLock
    • It provides readLock() and writeLock() methods, returning separate Lock objects. You acquire/release these specific locks.
    • The default implementation has specific fairness policies (check docs) and does not support upgrading from a read lock to a write lock.

11.1.1 Monitor-Based RW Lock Implementation

Java implementation of a basic RW lock with monitors.

class RWLock {
    int writers = 0; // Count of active writers (0 or 1)
    int readers = 0; // Count of active readers
 
    synchronized void acquire_read() {
        while (writers > 0) { // Wait if writer active
            try { wait(); } catch (InterruptedException e) {}
        }
        readers++;
    }
 
    synchronized void release_read() {
        readers--;
        if (readers == 0) notifyAll(); // Wake waiting writers if last reader
    }
 
    synchronized void acquire_write() {
        while (writers > 0 || readers > 0) { // Wait if writers OR readers active
            try { wait(); } catch (InterruptedException e) {}
        }
        writers++;
    }
 
    synchronized void release_write() {
        writers--;
        notifyAll(); // Wake waiting readers/writers
    }
}

Fairness:

  • This simple implementation has a reader preference.
  • If the lock is held for reading, new readers can acquire it immediately, even if a writer is waiting.
  • This can lead to writer starvation if reads are continuous.

11.1.2 Writer Priority RW Lock Implementation

We want to fix writer starvation using priority for writers.

class RWLock {
    int writers = 0;
    int readers = 0;
    int writersWaiting = 0; // Count waiting writers
 
    synchronized void acquire_read() {
        // Wait if writer active OR if writer waiting
        while (writers > 0 || writersWaiting > 0) {
            try { wait(); } catch (InterruptedException e) {}
        }
        readers++;
    }
    // release_read is the same (notifyAll if readers == 0)
 
    synchronized void acquire_write() {
        writersWaiting++; // Indicate intent to write
        while (writers > 0 || readers > 0) { // Wait if active reader/writer
            try { wait(); } catch (InterruptedException e) {}
        }
        writersWaiting--; // No longer waiting
        writers++;        // Become active writer
    }
   // release_write is the same (writers--, notifyAll)
}

Here when trying to read we wait until all waiting writers have finished.

Fairness: Is this fair now? It prevents writer starvation but might now starve readers if writers arrive continuously. True fairness is complex.