google-site-verification: google61fe8ba583a51912.html
StampedLock for Optimistic Reading in Java

Introduction to StampedLock

StampedLock introduced in Java 8 is a capability-based lock with three modes for controlling read/write access. It supports optimistic reading, which can dramatically improve performance in read-heavy scenarios by avoiding writer starvation.


1. Basic StampedLock Modes

Lock Modes Overview

import java.util.concurrent.locks.StampedLock; public class StampedLockBasic { private final StampedLock lock = new StampedLock(); private String data = "Initial Data"; // WRITE LOCK - Exclusive access public void writeData(String newData) { long stamp = lock.writeLock(); // Block until write lock acquired try { System.out.println(Thread.currentThread().getName() + " acquired write lock"); data = newData; Thread.sleep(1000); // Simulate write operation } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlockWrite(stamp); System.out.println(Thread.currentThread().getName() + " released write lock"); } } // READ LOCK - Shared access public String readData() { long stamp = lock.readLock(); // Block until read lock acquired try { System.out.println(Thread.currentThread().getName() + " acquired read lock"); Thread.sleep(500); // Simulate read operation return data; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } finally { lock.unlockRead(stamp); System.out.println(Thread.currentThread().getName() + " released read lock"); } } // OPTIMISTIC READ - No locking, but validation required public String optimisticRead() { long stamp = lock.tryOptimisticRead(); // Non-blocking String currentData = data; // Read data optimistically // Check if read was consistent (no write occurred during read) if (!lock.validate(stamp)) { System.out.println(Thread.currentThread().getName() + " optimistic read failed, upgrading to read lock"); // Fallback to read lock stamp = lock.readLock(); try { currentData = data; } finally { lock.unlockRead(stamp); } } else { System.out.println(Thread.currentThread().getName() + " optimistic read succeeded"); } return currentData; } public static void main(String[] args) throws InterruptedException { StampedLockBasic example = new StampedLockBasic(); // Writer thread Thread writer = new Thread(() -> { example.writeData("Updated Data"); }, "Writer-Thread"); // Reader threads Thread reader1 = new Thread(() -> { System.out.println("Read result: " + example.readData()); }, "Reader-1"); Thread reader2 = new Thread(() -> { System.out.println("Optimistic read result: " + example.optimisticRead()); }, "Reader-2"); writer.start(); Thread.sleep(100); // Let writer start first reader1.start(); reader2.start(); writer.join(); reader1.join(); reader2.join(); } }

Lock Mode Comparison

ModeCharacteristicsUse Case
Write LockExclusive, blockingData modification
Read LockShared, blockingSafe data reading
Optimistic ReadNon-blocking, requires validationRead-heavy, low contention

2. Optimistic Reading Patterns

Basic Optimistic Read Pattern

import java.util.concurrent.locks.StampedLock; import java.util.concurrent.ThreadLocalRandom; public class OptimisticReadingBasic { private final StampedLock lock = new StampedLock(); private int counter = 0; private String message = "Hello"; /** * Standard pattern for optimistic reading: * 1. tryOptimisticRead() - get stamp without blocking * 2. Read data fields * 3. validate(stamp) - check if read was consistent * 4. If invalid, fallback to read lock */ public String readDataOptimistically() { long stamp = lock.tryOptimisticRead(); // Capture all fields needed for consistent read int currentCounter = counter; String currentMessage = message; // Critical: Validate AFTER reading all fields if (!lock.validate(stamp)) { // Optimistic read failed - acquire read lock stamp = lock.readLock(); try { currentCounter = counter; currentMessage = message; } finally { lock.unlockRead(stamp); } } return String.format("Counter: %d, Message: %s", currentCounter, currentMessage); } public void updateData(int newCounter, String newMessage) { long stamp = lock.writeLock(); try { // Simulate some work Thread.sleep(ThreadLocalRandom.current().nextInt(50, 200)); counter = newCounter; message = newMessage; System.out.printf("Writer: Updated to counter=%d, message=%s%n", newCounter, newMessage); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlockWrite(stamp); } } public static void main(String[] args) throws InterruptedException { OptimisticReadingBasic example = new OptimisticReadingBasic(); // Start multiple readers for (int i = 0; i < 5; i++) { final int readerId = i; new Thread(() -> { for (int j = 0; j < 3; j++) { String result = example.readDataOptimistically(); System.out.printf("Reader-%d: %s%n", readerId, result); try { Thread.sleep(ThreadLocalRandom.current().nextInt(100, 300)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, "Reader-" + i).start(); } // Start a writer Thread writer = new Thread(() -> { for (int i = 1; i <= 3; i++) { example.updateData(i, "Message-" + i); try { Thread.sleep(500); // Write less frequently } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, "Writer"); writer.start(); writer.join(); } }

Complex Object Optimistic Reading

import java.util.concurrent.locks.StampedLock; import java.util.*; public class ComplexObjectOptimisticRead { private final StampedLock lock = new StampedLock(); private final Map<String, Integer> dataMap = new HashMap<>(); private int version = 0; private long lastUpdated = System.currentTimeMillis(); public ComplexObjectOptimisticRead() { // Initialize with some data dataMap.put("A", 1); dataMap.put("B", 2); dataMap.put("C", 3); } /** * Optimistic read for complex object state * Must capture ALL fields needed for consistent view */ public Snapshot getSnapshotOptimistically() { long stamp = lock.tryOptimisticRead(); // Capture all fields needed for consistent snapshot int currentVersion = version; long currentLastUpdated = lastUpdated; Map<String, Integer> currentDataMap = new HashMap<>(dataMap); // Defensive copy if (!lock.validate(stamp)) { // Fallback to read lock stamp = lock.readLock(); try { currentVersion = version; currentLastUpdated = lastUpdated; currentDataMap = new HashMap<>(dataMap); } finally { lock.unlockRead(stamp); } } return new Snapshot(currentVersion, currentLastUpdated, currentDataMap); } public void updateData(String key, Integer value) { long stamp = lock.writeLock(); try { version++; lastUpdated = System.currentTimeMillis(); dataMap.put(key, value); System.out.printf("Writer: Updated %s=%d (version=%d)%n", key, value, version); Thread.sleep(100); // Simulate write latency } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlockWrite(stamp); } } public void bulkUpdate(Map<String, Integer> updates) { long stamp = lock.writeLock(); try { version++; lastUpdated = System.currentTimeMillis(); dataMap.putAll(updates); System.out.printf("Writer: Bulk update (version=%d, %d entries)%n", version, updates.size()); Thread.sleep(200); // Simulate longer write operation } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlockWrite(stamp); } } // Snapshot class to hold consistent view public static class Snapshot { private final int version; private final long lastUpdated; private final Map<String, Integer> data; public Snapshot(int version, long lastUpdated, Map<String, Integer> data) { this.version = version; this.lastUpdated = lastUpdated; this.data = Collections.unmodifiableMap(new HashMap<>(data)); } public int getVersion() { return version; } public long getLastUpdated() { return lastUpdated; } public Map<String, Integer> getData() { return data; } @Override public String toString() { return String.format("Snapshot{version=%d, lastUpdated=%d, data=%s}", version, lastUpdated, data); } } public static void main(String[] args) throws InterruptedException { ComplexObjectOptimisticRead example = new ComplexObjectOptimisticRead(); // Start multiple readers List<Thread> readers = new ArrayList<>(); for (int i = 0; i < 3; i++) { final int readerId = i; Thread reader = new Thread(() -> { for (int j = 0; j < 5; j++) { Snapshot snapshot = example.getSnapshotOptimistically(); System.out.printf("Reader-%d: %s%n", readerId, snapshot); try { Thread.sleep(150); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, "Reader-" + i); readers.add(reader); reader.start(); } // Start writers Thread writer1 = new Thread(() -> { for (int i = 0; i < 3; i++) { example.updateData("Key-" + i, i * 10); try { Thread.sleep(300); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, "Writer-1"); Thread writer2 = new Thread(() -> { Map<String, Integer> bulkData = Map.of( "X", 100, "Y", 200, "Z", 300 ); example.bulkUpdate(bulkData); }, "Writer-2"); writer1.start(); writer2.start(); // Wait for completion writer1.join(); writer2.join(); for (Thread reader : readers) { reader.join(); } } }

3. Advanced Optimistic Patterns

Try-Convert Pattern

import java.util.concurrent.locks.StampedLock; import java.util.concurrent.ThreadLocalRandom; public class TryConvertPattern { private final StampedLock lock = new StampedLock(); private String data = "Initial"; private int readCount = 0; private int writeCount = 0; /** * Advanced pattern: tryConvertToWriteLock * Useful when read might need to upgrade to write */ public boolean updateIfCondition(String newData, String expectedCurrent) { long stamp = lock.readLock(); try { // Check condition under read lock while (data.equals(expectedCurrent)) { // Try to upgrade to write lock long writeStamp = lock.tryConvertToWriteLock(stamp); if (writeStamp != 0) { // Successfully upgraded stamp = writeStamp; data = newData; writeCount++; System.out.printf("Successfully updated: %s -> %s (converted)%n", expectedCurrent, newData); return true; } else { // Failed to upgrade, release read lock and acquire write lock lock.unlockRead(stamp); stamp = lock.writeLock(); // Re-check condition under write lock if (data.equals(expectedCurrent)) { data = newData; writeCount++; System.out.printf("Successfully updated: %s -> %s (write lock)%n", expectedCurrent, newData); return true; } else { // Condition changed, no update needed return false; } } } return false; // Condition not met } finally { lock.unlock(stamp); } } /** * Optimistic read with potential upgrade */ public String readAndConditionallyUpdate() { long stamp = lock.tryOptimisticRead(); String currentData = data; int currentReadCount = readCount; if (!lock.validate(stamp)) { // Fallback to read lock stamp = lock.readLock(); try { currentData = data; currentReadCount = readCount; } finally { lock.unlockRead(stamp); } } readCount++; // Conditionally update based on read if (currentReadCount % 5 == 0) { updateIfCondition("Updated-at-" + currentReadCount, currentData); } return currentData; } public String getData() { long stamp = lock.readLock(); try { return data; } finally { lock.unlockRead(stamp); } } public static void main(String[] args) throws InterruptedException { TryConvertPattern example = new TryConvertPattern(); // Start multiple worker threads List<Thread> workers = new ArrayList<>(); for (int i = 0; i < 5; i++) { final int workerId = i; Thread worker = new Thread(() -> { for (int j = 0; j < 10; j++) { String result = example.readAndConditionallyUpdate(); System.out.printf("Worker-%d: %s%n", workerId, result); try { Thread.sleep(ThreadLocalRandom.current().nextInt(50, 150)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, "Worker-" + i); workers.add(worker); worker.start(); } for (Thread worker : workers) { worker.join(); } System.out.println("Final data: " + example.getData()); } }

Time-Bounded Operations

import java.util.concurrent.locks.StampedLock; import java.util.concurrent.TimeUnit; import java.util.concurrent.ThreadLocalRandom; public class TimeBoundedOperations { private final StampedLock lock = new StampedLock(); private String data = "Initial Data"; private final long version = 1L; /** * Try optimistic read with timeout fallback */ public String readWithTimeout(long timeout, TimeUnit unit) { long startTime = System.nanoTime(); long timeoutNanos = unit.toNanos(timeout); // First attempt: optimistic read long stamp = lock.tryOptimisticRead(); String currentData = data; if (lock.validate(stamp)) { System.out.println(Thread.currentThread().getName() + ": Optimistic read succeeded"); return currentData; } // Optimistic read failed, try timed read lock try { stamp = lock.tryReadLock(timeoutNanos, TimeUnit.NANOSECONDS); if (stamp != 0) { try { long elapsed = System.nanoTime() - startTime; System.out.printf("%s: Read lock acquired after %d ms%n", Thread.currentThread().getName(), TimeUnit.NANOSECONDS.toMillis(elapsed)); currentData = data; return currentData; } finally { lock.unlockRead(stamp); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // Timeout or interrupt System.out.println(Thread.currentThread().getName() + ": Read timeout"); return "Timeout - unable to read"; } /** * Try write lock with timeout */ public boolean writeWithTimeout(String newData, long timeout, TimeUnit unit) { try { long stamp = lock.tryWriteLock(timeout, unit); if (stamp != 0) { try { data = newData; System.out.println(Thread.currentThread().getName() + ": Write completed"); return true; } finally { lock.unlockWrite(stamp); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println(Thread.currentThread().getName() + ": Write timeout"); return false; } /** * Optimistic read with retry mechanism */ public String optimisticReadWithRetry(int maxRetries) { for (int attempt = 1; attempt <= maxRetries; attempt++) { long stamp = lock.tryOptimisticRead(); String currentData = data; if (lock.validate(stamp)) { System.out.printf("%s: Optimistic read succeeded on attempt %d%n", Thread.currentThread().getName(), attempt); return currentData; } // Brief backoff before retry if (attempt < maxRetries) { try { Thread.sleep(ThreadLocalRandom.current().nextInt(10, 50)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } // All optimistic attempts failed, fallback to read lock long stamp = lock.readLock(); try { System.out.printf("%s: Fell back to read lock after %d retries%n", Thread.currentThread().getName(), maxRetries); return data; } finally { lock.unlockRead(stamp); } } public static void main(String[] args) throws InterruptedException { TimeBoundedOperations example = new TimeBoundedOperations(); // Writer thread that holds lock for varying durations Thread writer = new Thread(() -> { for (int i = 0; i < 3; i++) { boolean success = example.writeWithTimeout("Data-" + i, 2, TimeUnit.SECONDS); if (success) { try { // Hold write lock for varying times Thread.sleep(ThreadLocalRandom.current().nextInt(500, 1500)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }, "Writer"); // Reader threads with different strategies Thread timeoutReader = new Thread(() -> { for (int i = 0; i < 5; i++) { String result = example.readWithTimeout(500, TimeUnit.MILLISECONDS); System.out.println("TimeoutReader got: " + result); try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, "TimeoutReader"); Thread retryReader = new Thread(() -> { for (int i = 0; i < 5; i++) { String result = example.optimisticReadWithRetry(3); System.out.println("RetryReader got: " + result); try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }, "RetryReader"); writer.start(); timeoutReader.start(); retryReader.start(); writer.join(); timeoutReader.join(); retryReader.join(); } }

4. Performance Comparison

Benchmark: ReadLock vs Optimistic Read

import java.util.concurrent.locks.StampedLock; import java.util.concurrent.*; import java.util.concurrent.atomic.*; public class PerformanceComparison { private final StampedLock lock = new StampedLock(); private final AtomicLong data = new AtomicLong(0); private final int READER_COUNT = 10; private final int WRITER_COUNT = 2; private final int OPERATIONS_PER_THREAD = 10000; /** * Traditional read lock approach */ public long readWithReadLock() { long stamp = lock.readLock(); try { return data.get(); } finally { lock.unlockRead(stamp); } } /** * Optimistic read approach */ public long readWithOptimisticLock() { long stamp = lock.tryOptimisticRead(); long value = data.get(); if (!lock.validate(stamp)) { // Fallback to read lock stamp = lock.readLock(); try { value = data.get(); } finally { lock.unlockRead(stamp); } } return value; } public void write(long newValue) { long stamp = lock.writeLock(); try { data.set(newValue); } finally { lock.unlockWrite(stamp); } } public void runBenchmark() throws InterruptedException { System.out.println("=== StampedLock Performance Comparison ==="); System.out.printf("Readers: %d, Writers: %d, Operations per thread: %d%n%n", READER_COUNT, WRITER_COUNT, OPERATIONS_PER_THREAD); // Test with read locks testWithReadLocks(); // Test with optimistic reads testWithOptimisticReads(); } private void testWithReadLocks() throws InterruptedException { data.set(0); CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch endLatch = new CountDownLatch(READER_COUNT + WRITER_COUNT); AtomicLong totalReadTime = new AtomicLong(0); AtomicLong readCount = new AtomicLong(0); // Create readers for (int i = 0; i < READER_COUNT; i++) { new Thread(() -> { try { startLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } for (int j = 0; j < OPERATIONS_PER_THREAD; j++) { long startTime = System.nanoTime(); readWithReadLock(); long endTime = System.nanoTime(); totalReadTime.addAndGet(endTime - startTime); readCount.incrementAndGet(); } endLatch.countDown(); }).start(); } // Create writers for (int i = 0; i < WRITER_COUNT; i++) { new Thread(() -> { try { startLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } for (int j = 0; j < OPERATIONS_PER_THREAD / 10; j++) { // Fewer writes write(ThreadLocalRandom.current().nextLong()); try { Thread.sleep(1); // Simulate write work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } endLatch.countDown(); }).start(); } // Run test long startTime = System.nanoTime(); startLatch.countDown(); endLatch.await(); long totalTime = System.nanoTime() - startTime; System.out.println("=== READ LOCK RESULTS ==="); printResults(totalTime, totalReadTime.get(), readCount.get()); } private void testWithOptimisticReads() throws InterruptedException { data.set(0); CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch endLatch = new CountDownLatch(READER_COUNT + WRITER_COUNT); AtomicLong totalReadTime = new AtomicLong(0); AtomicLong readCount = new AtomicLong(0); AtomicLong optimisticSuccess = new AtomicLong(0); // Create readers for (int i = 0; i < READER_COUNT; i++) { new Thread(() -> { try { startLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } for (int j = 0; j < OPERATIONS_PER_THREAD; j++) { long startTime = System.nanoTime(); long value = readWithOptimisticLock(); long endTime = System.nanoTime(); totalReadTime.addAndGet(endTime - startTime); readCount.incrementAndGet(); // Track optimistic success rate (simplified) if (value >= 0) { // In real scenario, track actual optimistic success optimisticSuccess.incrementAndGet(); } } endLatch.countDown(); }).start(); } // Create writers (same as before) for (int i = 0; i < WRITER_COUNT; i++) { new Thread(() -> { try { startLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } for (int j = 0; j < OPERATIONS_PER_THREAD / 10; j++) { write(ThreadLocalRandom.current().nextLong()); try { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } endLatch.countDown(); }).start(); } // Run test long startTime = System.nanoTime(); startLatch.countDown(); endLatch.await(); long totalTime = System.nanoTime() - startTime; System.out.println("=== OPTIMISTIC READ RESULTS ==="); printResults(totalTime, totalReadTime.get(), readCount.get()); System.out.printf("Optimistic success rate: %.2f%%%n%n", (optimisticSuccess.get() * 100.0 / readCount.get())); } private void printResults(long totalTime, long totalReadTime, long readCount) { double totalMs = totalTime / 1_000_000.0; double avgReadTimeNs = totalReadTime / (double) readCount; System.out.printf("Total time: %.2f ms%n", totalMs); System.out.printf("Average read time: %.2f ns%n", avgReadTimeNs); System.out.printf("Throughput: %.2f operations/ms%n%n", readCount / totalMs); } public static void main(String[] args) throws InterruptedException { PerformanceComparison benchmark = new PerformanceComparison(); benchmark.runBenchmark(); } }

5. Real-World Use Cases

Cached Data with StampedLock

import java.util.concurrent.locks.StampedLock; import java.util.*; import java.util.concurrent.*; public class CachedDataWithStampedLock { private final StampedLock lock = new StampedLock(); private final Map<String, CacheEntry> cache = new HashMap<>(); private final long cacheTimeoutMs; public CachedDataWithStampedLock(long cacheTimeoutMs) { this.cacheTimeoutMs = cacheTimeoutMs; } /** * Get data with optimistic read for cache hit */ public String get(String key) { // First attempt: optimistic read long stamp = lock.tryOptimisticRead(); CacheEntry entry = cache.get(key); if (entry != null && !isExpired(entry) && lock.validate(stamp)) { // Fast path: cache hit with optimistic read recordCacheHit("optimistic"); return entry.data; } // Slow path: acquire read lock stamp = lock.readLock(); try { entry = cache.get(key); if (entry != null && !isExpired(entry)) { recordCacheHit("readLock"); return entry.data; } } finally { lock.unlockRead(stamp); } // Cache miss - load data return loadData(key); } /** * Load data with write lock */ private String loadData(String key) { long stamp = lock.writeLock(); try { // Double-check under write lock CacheEntry existing = cache.get(key); if (existing != null && !isExpired(existing)) { recordCacheHit("writeLock-doubleCheck"); return existing.data; } // Simulate expensive data loading String data = expensiveLoadOperation(key); cache.put(key, new CacheEntry(data, System.currentTimeMillis())); recordCacheMiss(); return data; } finally { lock.unlockWrite(stamp); } } /** * Bulk update cache */ public void bulkUpdate(Map<String, String> newData) { long stamp = lock.writeLock(); try { long now = System.currentTimeMillis(); for (Map.Entry<String, String> entry : newData.entrySet()) { cache.put(entry.getKey(), new CacheEntry(entry.getValue(), now)); } System.out.println("Bulk updated " + newData.size() + " entries"); } finally { lock.unlockWrite(stamp); } } /** * Clear expired entries */ public void cleanup() { long stamp = lock.writeLock(); try { Iterator<Map.Entry<String, CacheEntry>> it = cache.entrySet().iterator(); int removed = 0; while (it.hasNext()) { if (isExpired(it.next().getValue())) { it.remove(); removed++; } } System.out.println("Cleaned up " + removed + " expired entries"); } finally { lock.unlockWrite(stamp); } } private boolean isExpired(CacheEntry entry) { return System.currentTimeMillis() - entry.timestamp > cacheTimeoutMs; } private String expensiveLoadOperation(String key) { try { Thread.sleep(100); // Simulate expensive operation return "Data-for-" + key + "-" + System.currentTimeMillis(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Load interrupted", e); } } // Statistics private final Map<String, AtomicLong> stats = new ConcurrentHashMap<>(); private void recordCacheHit(String type) { stats.computeIfAbsent("hit." + type, k -> new AtomicLong()).incrementAndGet(); } private void recordCacheMiss() { stats.computeIfAbsent("miss", k -> new AtomicLong()).incrementAndGet(); } public void printStats() { System.out.println("\n=== Cache Statistics ==="); stats.forEach((key, value) -> System.out.printf("%s: %d%n", key, value.get())); } private static class CacheEntry { final String data; final long timestamp; CacheEntry(String data, long timestamp) { this.data = data; this.timestamp = timestamp; } } public static void main(String[] args) throws InterruptedException { CachedDataWithStampedLock cache = new CachedDataWithStampedLock(5000); // 5 second timeout // Start readers List<Thread> readers = new ArrayList<>(); for (int i = 0; i < 10; i++) { final int readerId = i; Thread reader = new Thread(() -> { for (int j = 0; j < 20; j++) { String key = "key-" + (j % 5); // Limited key set for cache hits String data = cache.get(key); System.out.printf("Reader-%d: %s = %s%n", readerId, key, data); try { Thread.sleep(ThreadLocalRandom.current().nextInt(50, 200)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); readers.add(reader); reader.start(); } // Start periodic writer Thread writer = new Thread(() -> { for (int i = 0; i < 3; i++) { Map<String, String> updates = Map.of( "key-0", "updated-" + i, "key-1", "updated-" + i, "key-6", "new-key-" + i ); cache.bulkUpdate(updates); try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); writer.start(); // Start cleanup Thread cleanup = new Thread(() -> { for (int i = 0; i < 2; i++) { try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } cache.cleanup(); } }); cleanup.start(); // Wait for completion for (Thread reader : readers) { reader.join(); } writer.join(); cleanup.join(); cache.printStats(); } }

6. Best Practices and Pitfalls

StampedLock Best Practices

import java.util.concurrent.locks.StampedLock; public class StampedLockBestPractices { private final StampedLock lock = new StampedLock(); private String data1 = "Data1"; private String data2 = "Data2"; private int version = 0; /** * GOOD: Proper optimistic read pattern */ public String readConsistentDataGood() { long stamp = lock.tryOptimisticRead(); // Read ALL related fields before validation String currentData1 = data1; String currentData2 = data2; int currentVersion = version; // Validate AFTER reading all fields if (!lock.validate(stamp)) { // Fallback to read lock stamp = lock.readLock(); try { currentData1 = data1; currentData2 = data2; currentVersion = version; } finally { lock.unlockRead(stamp); } } return String.format("%s, %s (v%d)", currentData1, currentData2, currentVersion); } /** * BAD: Incorrect optimistic read - validation between reads */ public String readConsistentDataBad() { long stamp = lock.tryOptimisticRead(); String currentData1 = data1; // WRONG: Validating between reads breaks consistency if (!lock.validate(stamp)) { stamp = lock.readLock(); try { currentData1 = data1; } finally { lock.unlockRead(stamp); } } String currentData2 = data2; // This might be inconsistent with data1! return String.format("%s, %s", currentData1, currentData2); } /** * GOOD: Always use try-finally with locks */ public void updateDataGood(String newData1, String newData2) { long stamp = lock.writeLock(); try { data1 = newData1; data2 = newData2; version++; } finally { lock.unlockWrite(stamp); // ALWAYS in finally block } } /** * BAD: Risk of not releasing lock on exception */ public void updateDataBad(String newData1, String newData2) { long stamp = lock.writeLock(); data1 = newData1; data2 = newData2; version++; lock.unlockWrite(stamp); // Not in finally - risky! } /** * GOOD: Proper tryConvert usage */ public boolean conditionalUpdateGood(String expected, String newValue) { long stamp = lock.readLock(); try { while (data1.equals(expected)) { long writeStamp = lock.tryConvertToWriteLock(stamp); if (writeStamp != 0) { stamp = writeStamp; data1 = newValue; return true; } else { lock.unlockRead(stamp); stamp = lock.writeLock(); // Re-check condition under write lock if (data1.equals(expected)) { data1 = newValue; return true; } return false; } } return false; } finally { lock.unlock(stamp); } } /** * AVOID: Using synchronized with StampedLock */ public void methodWithSynchronized() { synchronized (this) { // This can cause deadlocks with StampedLock long stamp = lock.writeLock(); // DANGEROUS! try { // ... } finally { lock.unlockWrite(stamp); } } } /** * GOOD: Use ThreadLocalRandom instead of Random in concurrent code */ public void concurrentRandomAccess() { long stamp = lock.readLock(); try { // ThreadLocalRandom is thread-safe and faster int randomValue = java.util.concurrent.ThreadLocalRandom.current().nextInt(100); // Use randomValue... } finally { lock.unlockRead(stamp); } } }

Summary

Key Benefits of StampedLock Optimistic Reading:

  1. Performance: Avoids lock acquisition for read-heavy, low-contention scenarios
  2. Non-blocking: Optimistic reads never block
  3. Writer Fairness: Prevents writer starvation common in ReadWriteLock
  4. Flexibility: Multiple lock modes and conversion capabilities

When to Use Optimistic Reading:

  • Read-heavy workloads with infrequent writes
  • Low contention scenarios
  • Large read operations where lock acquisition overhead is significant
  • When reads can tolerate occasional retries

Best Practices:

  1. Always validate optimistic reads after reading all fields
  2. Use try-finally for lock cleanup
  3. Provide fallback to read lock when optimistic read fails
  4. Avoid nested locking with other synchronization mechanisms
  5. Use tryConvertToWriteLock for read-to-write upgrades

Performance Characteristics:

  • Optimistic Read: ~10-50ns (when successful)
  • Read Lock: ~100-200ns
  • Write Lock: ~200-500ns
  • Optimistic Fallback: Additional cost of read lock acquisition

StampedLock with optimistic reading provides excellent performance for read-dominated workloads while maintaining data consistency and writer fairness.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper