Back to Locks: ReentrantLock, ReadWriteLock, StampedLock
Q3Question 3 of 7

What is StampedLock?

Key Concepts
  • StampedLock (Java 8) provides three modes: write lock (exclusive), read lock (shared), and optimistic read (no lock, just validate).
  • Optimistic read calls tryOptimisticRead() to get a stamp, reads data into local variables, then calls validate(stamp) to check if a write occurred.
  • If no write occurred during the read, the data is valid with zero lock overhead; if a write did occur, fall back to a regular read lock.
  • StampedLock beats ReadWriteLock for read-heavy workloads because optimistic read avoids the CAS on a shared reader count that ReadWriteLock requires.
  • StampedLock is NOT reentrant: locking twice from the same thread causes a deadlock.
  • StampedLock does not support Condition objects, unlike ReentrantLock.
  • Always copy shared data to local variables before calling validate(), because the data may change between validate() and read.

Definition

StampedLock adds an optimistic read mode to read-write locking: tryOptimisticRead() returns a stamp without acquiring any lock, and validate(stamp) confirms no write occurred, giving zero-lock overhead for read-heavy workloads.
  • Write lock – exclusive, like ReentrantReadWriteLock's writeLock(). Only one thread at a time.
  • Read lock – shared, like ReadWriteLock's readLock(). Multiple concurrent readers, but blocks writers.
  • Optimistic read – no lock acquired. tryOptimisticRead() returns a stamp (a volatile read of the internal version number). Read shared data into local variables. validate(stamp) checks if any write lock was acquired since the stamp was issued. If valid, use the local copies with zero lock overhead. If invalid, fall back to a regular readLock.
  • Why faster – ReadWriteLock's readLock() requires a CAS on a shared reader count. Under 100 concurrent readers, all 100 threads CAS the same counter, causing cache-line contention. StampedLock's optimistic read is a single volatile read with zero contention.
  • Limitations – NOT reentrant (locking twice from the same thread causes deadlock). No newCondition(). Optimistic reads must be SHORT because the longer the read, the higher the chance a write invalidates it.

At National Bank of India, ReadWriteLock is like a sign-in sheet at the vault door: every teller reading the balance register must sign in and sign out. Even though readers do not interfere with each other, the sign-in sheet itself becomes a bottleneck. StampedLock's optimistic read is like glancing at the register without signing in, noting the page number (stamp), reading the balance, then checking if anyone flipped the page since you looked (validate). If nobody did, your balance is correct with zero overhead. If someone did, you sign in properly and read again.

Understanding the Concepts

What is StampedLock?

StampedLock, introduced in Java 8, extends the concept of read-write locking with an optimistic read mode that avoids acquiring any lock at all. It provides three access modes, each returning a stamp that must be used to unlock or validate.

How do the three modes work?

JAVA
package com.bank.concurrency;

import java.math.BigDecimal;
import java.util.concurrent.locks.StampedLock;

public class StampedLockDemo {
    private final StampedLock sl = new StampedLock();
    private BigDecimal balance = new BigDecimal("100000.00");
    private BigDecimal interestRate = new BigDecimal("7.5");

    // MODE 1: Write lock (exclusive)
    public void debit(BigDecimal amount) {
        long stamp = sl.writeLock();
        try {
            balance = balance.subtract(amount);
        } finally {
            sl.unlockWrite(stamp);
        }
    }

    // MODE 2: Read lock (shared, blocks writers)
    public BigDecimal getBalanceLocked() {
        long stamp = sl.readLock();
        try {
            return balance;
        } finally {
            sl.unlockRead(stamp);
        }
    }

    // MODE 3: Optimistic read (NO LOCK!)
    public BigDecimal getBalanceOptimistic() {
        long stamp = sl.tryOptimisticRead();   // get stamp, NO lock
        BigDecimal localBalance = balance;      // copy to local variable
        BigDecimal localRate = interestRate;     // copy to local variable
        if (sl.validate(stamp)) {               // any write since stamp?
            return localBalance;                 // NO write: data valid!
        }
        // Write happened: fall back to read lock
        stamp = sl.readLock();
        try {
            return balance;
        } finally {
            sl.unlockRead(stamp);
        }
    }
}

How is StampedLock different from ReadWriteLock?

| Feature | StampedLock | ReentrantReadWriteLock |
|---|---|---|
| Optimistic read | Yes (zero lock overhead) | No |
| Read lock CAS | No (volatile read only) | Yes (CAS on reader count) |
| Reentrant | No (deadlock on re-lock!) | Yes |
| Condition support | No | Yes (on writeLock) |
| Fairness option | No | Yes |
| Lock downgrade | Yes (write to read) | Yes |
| Lock upgrade | Yes (read to write via tryConvertToWriteLock) | No |
| Best for | 99:1 read:write ratio | 95:5 read:write ratio |

Why is optimistic read faster?

ReadWriteLock's readLock() must atomically increment a shared reader count using CAS. Under 100 concurrent reader threads, all 100 threads CAS the same shared counter, causing cache-line ping-pong and contention. StampedLock's tryOptimisticRead() is a single volatile read of the internal stamp (a long value that changes only when a write lock is acquired or released). Volatile reads do not modify shared state, so there is zero contention even with 1000 concurrent readers.

What are the rules for optimistic read?

You must copy all shared data to local variables before calling validate(). You cannot read shared data after validate() because a write could happen between validate() returning true and the subsequent read.

JAVA
// WRONG: read after validate
long stamp = sl.tryOptimisticRead();
if (sl.validate(stamp)) {
    return balance;  // WRONG: write may happen between validate and return!
}

// CORRECT: copy before validate
long stamp = sl.tryOptimisticRead();
BigDecimal local = balance;    // copy first
if (sl.validate(stamp)) {
    return local;              // use the copy
}

What are the common mistakes?

The most dangerous mistake is re-locking from the same thread. StampedLock is NOT reentrant. If a thread holds a read lock and tries to acquire a write lock (or vice versa), it deadlocks against itself.

JAVA
// WRONG: re-locking causes DEADLOCK
long stamp = sl.readLock();
try {
    long writeStamp = sl.writeLock();  // DEADLOCK! Same thread, not reentrant
} finally {
    sl.unlockRead(stamp);
}

// CORRECT: use tryConvertToWriteLock for upgrade
long stamp = sl.readLock();
try {
    long writeStamp = sl.tryConvertToWriteLock(stamp);
    if (writeStamp != 0L) {
        stamp = writeStamp;  // upgraded!
        balance = balance.add(interest);
    } else {
        sl.unlockRead(stamp);
        stamp = sl.writeLock();  // full write lock
        balance = balance.add(interest);
    }
} finally {
    sl.unlock(stamp);
}

When should you use StampedLock?

Use StampedLock when reads vastly outnumber writes (99:1 or higher) and reads are short. The optimistic path handles the common case with zero overhead, and the rare write invalidation triggers a fallback to a full read lock.

What are common interview follow-up questions?

  • Can StampedLock replace ReentrantLock? No, because StampedLock is not reentrant and does not support Conditions.
  • Is the optimistic stamp thread-safe? The stamp is a long value local to the reading thread. It does not need to be shared.

Visual Overview

DIAGRAM
  StampedLock: Optimistic Read Flow
  ═════════════════════════════════

  Reader Thread                    Writer Thread
  ─────────────                    ─────────────
  stamp = tryOptimisticRead()
  local = balance  (copy)
  validate(stamp)?
    │
    ├── YES (no write) ──→ return local  (zero lock!)
    │
    └── NO (write occurred)
         │
         ▼
    stamp = readLock()             writeLock()
    return balance                 balance -= amount
    unlockRead(stamp)              unlockWrite(stamp)

Key Takeaway

StampedLock adds optimistic reading to the read-write locking pattern. tryOptimisticRead() returns a stamp without acquiring any lock, just a volatile read of the internal version number. You copy shared data to local variables, then call validate(stamp). If no write lock was acquired since the stamp was issued, the data is valid with zero lock overhead. If a write occurred, you fall back to a regular readLock.

This beats ReentrantReadWriteLock for read-heavy workloads because ReadWriteLock's readLock() requires a CAS on a shared reader count that causes contention under many concurrent readers. StampedLock's optimistic read has zero contention because it only performs a volatile read. The trade-off is that StampedLock is not reentrant and does not support Conditions.

At National Bank of India, a balance cache serving 10,000 reads per second with only 10 writes per second achieved P99 latency of 0.1 milliseconds with StampedLock, compared to 2 milliseconds with ReadWriteLock. Over 99.9% of reads completed on the optimistic path with no lock overhead.

Remember one sentence: StampedLock's optimistic read gives zero-lock overhead for readers by validating a stamp instead of acquiring a lock, making it ideal for read-heavy workloads.

---

Share
Use ← → arrow keys to navigate