ReadWriteLock is a sophisticated locking mechanism that allows multiple threads to read a shared resource simultaneously, while ensuring exclusive access for write operations. This provides better performance in read-heavy scenarios compared to traditional synchronized blocks or ReentrantLock.
1. ReadWriteLock Fundamentals
What is ReadWriteLock?
- Multiple readers can access the resource concurrently
- Single writer gets exclusive access (no readers or other writers)
- Write-preferring or Read-preferring policies available
- Improves performance for read-heavy workloads
Key Components
- ReentrantReadWriteLock - Main implementation
- ReadLock - Shared lock for read operations
- WriteLock - Exclusive lock for write operations
2. Basic ReadWriteLock Usage
Simple ReadWriteLock Example
import java.util.concurrent.locks.*;
public class BasicReadWriteLockExample {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReadWriteLock.ReadLock readLock = lock.readLock();
private final ReadWriteLock.WriteLock writeLock = lock.writeLock();
private String sharedData = "Initial Data";
private int readCount = 0;
private int writeCount = 0;
public String readData() {
readLock.lock();
try {
// Multiple threads can read simultaneously
readCount++;
System.out.println(Thread.currentThread().getName() + " reading data: " + sharedData);
Thread.sleep(100); // Simulate read operation
return sharedData;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
readLock.unlock();
}
}
public void writeData(String newData) {
writeLock.lock();
try {
// Only one thread can write at a time
writeCount++;
System.out.println(Thread.currentThread().getName() + " writing data: " + newData);
Thread.sleep(200); // Simulate write operation
sharedData = newData;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
writeLock.unlock();
}
}
public void printStats() {
readLock.lock();
try {
System.out.println("Read operations: " + readCount + ", Write operations: " + writeCount);
} finally {
readLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
BasicReadWriteLockExample example = new BasicReadWriteLockExample();
// Create multiple reader threads
Thread[] readers = new Thread[5];
for (int i = 0; i < readers.length; i++) {
readers[i] = new Thread(() -> {
for (int j = 0; j < 3; j++) {
example.readData();
}
}, "Reader-" + (i + 1));
}
// Create writer threads
Thread[] writers = new Thread[2];
for (int i = 0; i < writers.length; i++) {
writers[i] = new Thread(() -> {
for (int j = 0; j < 2; j++) {
example.writeData("Data from " + Thread.currentThread().getName());
}
}, "Writer-" + (i + 1));
}
// Start all threads
for (Thread reader : readers) reader.start();
for (Thread writer : writers) writer.start();
// Wait for completion
for (Thread reader : readers) reader.join();
for (Thread writer : writers) writer.join();
example.printStats();
}
}
TryLock with ReadWriteLock
import java.util.concurrent.locks.*;
import java.util.concurrent.TimeUnit;
public class TryLockExample {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private String data = "Initial";
private boolean resourceAvailable = true;
public String tryReadData(long timeout, TimeUnit unit) {
try {
if (readLock.tryLock(timeout, unit)) {
try {
System.out.println(Thread.currentThread().getName() + " acquired read lock");
Thread.sleep(100);
return data;
} finally {
readLock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " could not acquire read lock");
return "Read timeout";
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "Interrupted";
}
}
public boolean tryWriteData(String newData, long timeout, TimeUnit unit) {
try {
if (writeLock.tryLock(timeout, unit)) {
try {
System.out.println(Thread.currentThread().getName() + " acquired write lock");
Thread.sleep(200);
data = newData;
return true;
} finally {
writeLock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " could not acquire write lock");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
public static void main(String[] args) throws InterruptedException {
TryLockExample example = new TryLockExample();
// Thread that will hold write lock for a long time
Thread longWriter = new Thread(() -> {
example.writeLock.lock();
try {
System.out.println("Long writer holding lock...");
Thread.sleep(3000); // Hold lock for 3 seconds
example.data = "Updated by long writer";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
example.writeLock.unlock();
}
}, "LongWriter");
// Threads that will try to acquire locks with timeout
Thread tryingReader = new Thread(() -> {
String result = example.tryReadData(1, TimeUnit.SECONDS);
System.out.println("Trying reader result: " + result);
}, "TryingReader");
Thread tryingWriter = new Thread(() -> {
boolean success = example.tryWriteData("New data", 1, TimeUnit.SECONDS);
System.out.println("Trying writer success: " + success);
}, "TryingWriter");
longWriter.start();
Thread.sleep(100); // Ensure long writer gets lock first
tryingReader.start();
tryingWriter.start();
longWriter.join();
tryingReader.join();
tryingWriter.join();
}
}
3. Real-World Use Cases
Use Case 1: Thread-Safe Cache with ReadWriteLock
import java.util.concurrent.locks.*;
import java.util.*;
public class ReadWriteLockCache<K, V> {
private final Map<K, V> cache = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private int hitCount = 0;
private int missCount = 0;
public V get(K key) {
readLock.lock();
try {
V value = cache.get(key);
if (value != null) {
hitCount++;
return value;
}
} finally {
readLock.unlock();
}
// Cache miss - compute value (without holding read lock)
V computedValue = computeValue(key);
writeLock.lock();
try {
// Double-check after acquiring write lock
V existingValue = cache.get(key);
if (existingValue != null) {
// Another thread already computed the value
return existingValue;
}
cache.put(key, computedValue);
missCount++;
return computedValue;
} finally {
writeLock.unlock();
}
}
public void put(K key, V value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
public boolean containsKey(K key) {
readLock.lock();
try {
return cache.containsKey(key);
} finally {
readLock.unlock();
}
}
public V remove(K key) {
writeLock.lock();
try {
return cache.remove(key);
} finally {
writeLock.unlock();
}
}
public void clear() {
writeLock.lock();
try {
cache.clear();
hitCount = 0;
missCount = 0;
} finally {
writeLock.unlock();
}
}
public int size() {
readLock.lock();
try {
return cache.size();
} finally {
readLock.unlock();
}
}
public Map<K, V> snapshot() {
readLock.lock();
try {
return new HashMap<>(cache);
} finally {
readLock.unlock();
}
}
public CacheStats getStats() {
readLock.lock();
try {
return new CacheStats(hitCount, missCount, cache.size());
} finally {
readLock.unlock();
}
}
@SuppressWarnings("unchecked")
private V computeValue(K key) {
// Simulate expensive computation
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return (V) ("ComputedValueFor_" + key);
}
public static class CacheStats {
private final int hitCount;
private final int missCount;
private final int size;
public CacheStats(int hitCount, int missCount, int size) {
this.hitCount = hitCount;
this.missCount = missCount;
this.size = size;
}
public double hitRatio() {
long total = hitCount + missCount;
return total == 0 ? 0.0 : (double) hitCount / total;
}
@Override
public String toString() {
return String.format("CacheStats{size=%d, hits=%d, misses=%d, hitRatio=%.2f}",
size, hitCount, missCount, hitRatio());
}
}
public static void main(String[] args) throws InterruptedException {
ReadWriteLockCache<String, String> cache = new ReadWriteLockCache<>();
int threadCount = 10;
int operationsPerThread = 100;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
threads[i] = new Thread(() -> {
Random random = new Random();
for (int j = 0; j < operationsPerThread; j++) {
String key = "key-" + random.nextInt(50); // Limited key space for cache hits
if (random.nextDouble() < 0.8) {
// 80% reads
cache.get(key);
} else {
// 20% writes
cache.put(key, "value-from-thread-" + threadId);
}
}
});
}
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join();
System.out.println("Final cache stats: " + cache.getStats());
System.out.println("Cache contents: " + cache.snapshot());
}
}
Use Case 2: Configuration Manager with ReadWriteLock
import java.util.concurrent.locks.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class ConfigurationManager {
private final Properties config = new Properties();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); // Fair lock
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private final List<ConfigListener> listeners = new ArrayList<>();
private volatile long lastModified;
public ConfigurationManager() {
// Initial configuration
config.setProperty("database.url", "jdbc:mysql://localhost:3306/mydb");
config.setProperty("database.username", "admin");
config.setProperty("database.password", "secret");
config.setProperty("cache.size", "1000");
config.setProperty("thread.pool.size", "10");
lastModified = System.currentTimeMillis();
}
public String getConfig(String key) {
readLock.lock();
try {
return config.getProperty(key);
} finally {
readLock.unlock();
}
}
public String getConfig(String key, String defaultValue) {
readLock.lock();
try {
return config.getProperty(key, defaultValue);
} finally {
readLock.unlock();
}
}
public Properties getAllConfig() {
readLock.lock();
try {
return new Properties(config); // Return copy
} finally {
readLock.unlock();
}
}
public void setConfig(String key, String value) {
writeLock.lock();
try {
String oldValue = config.getProperty(key);
config.setProperty(key, value);
lastModified = System.currentTimeMillis();
// Notify listeners
if (!Objects.equals(oldValue, value)) {
notifyListeners(key, oldValue, value);
}
} finally {
writeLock.unlock();
}
}
public void bulkUpdate(Map<String, String> updates) {
writeLock.lock();
try {
Map<String, String> changes = new HashMap<>();
for (Map.Entry<String, String> entry : updates.entrySet()) {
String oldValue = config.getProperty(entry.getKey());
config.setProperty(entry.getKey(), entry.getValue());
if (!Objects.equals(oldValue, entry.getValue())) {
changes.put(entry.getKey(), oldValue);
}
}
lastModified = System.currentTimeMillis();
// Notify listeners for each change
for (Map.Entry<String, String> change : changes.entrySet()) {
notifyListeners(change.getKey(), change.getValue(), updates.get(change.getKey()));
}
} finally {
writeLock.unlock();
}
}
public boolean reloadFromExternalSource(Properties newConfig) {
// Try to acquire write lock with timeout to avoid blocking during reload
try {
if (writeLock.tryLock(5, TimeUnit.SECONDS)) {
try {
Map<String, String> changes = findChanges(newConfig);
config.clear();
config.putAll(newConfig);
lastModified = System.currentTimeMillis();
// Notify listeners of all changes
for (Map.Entry<String, String> change : changes.entrySet()) {
notifyListeners(change.getKey(), change.getValue(),
newConfig.getProperty(change.getKey()));
}
return true;
} finally {
writeLock.unlock();
}
} else {
System.out.println("Could not acquire write lock for reload - configuration busy");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
private Map<String, String> findChanges(Properties newConfig) {
Map<String, String> changes = new HashMap<>();
// Check for modified or removed properties
for (String key : config.stringPropertyNames()) {
String oldValue = config.getProperty(key);
String newValue = newConfig.getProperty(key);
if (!Objects.equals(oldValue, newValue)) {
changes.put(key, oldValue);
}
}
// Check for new properties
for (String key : newConfig.stringPropertyNames()) {
if (!config.containsKey(key)) {
changes.put(key, null);
}
}
return changes;
}
// Listener management
public void addListener(ConfigListener listener) {
writeLock.lock();
try {
listeners.add(listener);
} finally {
writeLock.unlock();
}
}
public void removeListener(ConfigListener listener) {
writeLock.lock();
try {
listeners.remove(listener);
} finally {
writeLock.unlock();
}
}
private void notifyListeners(String key, String oldValue, String newValue) {
// Use read lock for notification to allow concurrent reads during notifications
readLock.lock();
try {
List<ConfigListener> currentListeners = new ArrayList<>(listeners);
readLock.unlock(); // Release read lock before calling external code
for (ConfigListener listener : currentListeners) {
try {
listener.onConfigChanged(key, oldValue, newValue);
} catch (Exception e) {
System.err.println("Error in config listener: " + e.getMessage());
}
}
} finally {
// If we still hold the read lock, release it
if (lock.getReadHoldCount() > 0) {
readLock.unlock();
}
}
}
public long getLastModified() {
// No lock needed for volatile read
return lastModified;
}
public interface ConfigListener {
void onConfigChanged(String key, String oldValue, String newValue);
}
// Example usage
public static void main(String[] args) throws InterruptedException {
ConfigurationManager configManager = new ConfigurationManager();
// Add a listener
configManager.addListener((key, oldValue, newValue) -> {
System.out.printf("Config changed: %s = %s -> %s%n", key, oldValue, newValue);
});
// Multiple reader threads
Thread[] readers = new Thread[5];
for (int i = 0; i < readers.length; i++) {
readers[i] = new Thread(() -> {
Random random = new Random();
for (int j = 0; j < 10; j++) {
String value = configManager.getConfig("database.url");
System.out.println(Thread.currentThread().getName() + " read: " + value);
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "Reader-" + i);
}
// Writer thread
Thread writer = new Thread(() -> {
for (int i = 0; i < 3; i++) {
configManager.setConfig("cache.size", String.valueOf(1000 + i * 100));
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "Writer");
// Start all threads
for (Thread reader : readers) reader.start();
writer.start();
// Wait for completion
for (Thread reader : readers) reader.join();
writer.join();
System.out.println("Final configuration: " + configManager.getAllConfig());
}
}
4. Advanced ReadWriteLock Patterns
Lock Downgrading Pattern
import java.util.concurrent.locks.*;
public class LockDowngradingExample {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private volatile String data;
private volatile boolean initialized = false;
public void initializeIfNeeded() {
// First, try with read lock (fast path)
if (initialized) {
return;
}
// Not initialized, acquire write lock
writeLock.lock();
try {
// Double-check inside write lock
if (!initialized) {
System.out.println(Thread.currentThread().getName() + " initializing data...");
// Simulate expensive initialization
Thread.sleep(1000);
data = "Initialized Data";
initialized = true;
// Downgrade to read lock before performing read-heavy operations
readLock.lock();
try {
writeLock.unlock(); // Release write lock but keep read lock
// Now we have read lock, other readers can proceed
performReadOperations();
} finally {
readLock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// Ensure write lock is released if we still hold it
if (lock.isWriteLockedByCurrentThread()) {
writeLock.unlock();
}
}
}
private void performReadOperations() {
// This method can be called by multiple threads concurrently
System.out.println(Thread.currentThread().getName() + " performing read operations on: " + data);
// Simulate read-heavy work
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public String getData() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
LockDowngradingExample example = new LockDowngradingExample();
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
example.initializeIfNeeded();
System.out.println(Thread.currentThread().getName() + " got data: " + example.getData());
}, "Thread-" + i);
}
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join();
}
}
ReadWriteLock with Condition
import java.util.concurrent.locks.*;
import java.util.*;
public class ReadWriteLockWithCondition {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private final Condition dataAvailable = writeLock.newCondition();
private final Condition spaceAvailable = writeLock.newCondition();
private final Queue<String> queue = new LinkedList<>();
private final int maxSize = 10;
private boolean shutdown = false;
public void produce(String item) throws InterruptedException {
writeLock.lock();
try {
while (queue.size() == maxSize && !shutdown) {
System.out.println(Thread.currentThread().getName() + " waiting for space...");
spaceAvailable.await();
}
if (shutdown) {
System.out.println("Producer shutting down");
return;
}
queue.offer(item);
System.out.println(Thread.currentThread().getName() + " produced: " + item);
dataAvailable.signalAll(); // Notify all consumers
} finally {
writeLock.unlock();
}
}
public String consume() throws InterruptedException {
writeLock.lock();
try {
while (queue.isEmpty() && !shutdown) {
System.out.println(Thread.currentThread().getName() + " waiting for data...");
dataAvailable.await();
}
if (shutdown && queue.isEmpty()) {
return null; // Shutdown signal
}
String item = queue.poll();
System.out.println(Thread.currentThread().getName() + " consumed: " + item);
spaceAvailable.signalAll(); // Notify all producers
return item;
} finally {
writeLock.unlock();
}
}
public List<String> snapshot() {
readLock.lock();
try {
return new ArrayList<>(queue);
} finally {
readLock.unlock();
}
}
public int size() {
readLock.lock();
try {
return queue.size();
} finally {
readLock.unlock();
}
}
public void shutdown() {
writeLock.lock();
try {
shutdown = true;
dataAvailable.signalAll(); // Wake up all waiting consumers
spaceAvailable.signalAll(); // Wake up all waiting producers
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReadWriteLockWithCondition example = new ReadWriteLockWithCondition();
// Producers
Thread[] producers = new Thread[3];
for (int i = 0; i < producers.length; i++) {
final int producerId = i;
producers[i] = new Thread(() -> {
try {
for (int j = 0; j < 5; j++) {
example.produce("item-" + producerId + "-" + j);
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Producer-" + i);
}
// Consumers
Thread[] consumers = new Thread[2];
for (int i = 0; i < consumers.length; i++) {
consumers[i] = new Thread(() -> {
try {
while (true) {
String item = example.consume();
if (item == null) break; // Shutdown signal
Thread.sleep(150);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Consumer-" + i);
}
// Monitor thread
Thread monitor = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
System.out.println("Queue snapshot: " + example.snapshot() + " (size: " + example.size() + ")");
Thread.sleep(200);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Monitor");
// Start all threads
for (Thread producer : producers) producer.start();
for (Thread consumer : consumers) consumer.start();
monitor.start();
// Wait for producers to finish
for (Thread producer : producers) producer.join();
// Shutdown and wait for consumers
Thread.sleep(1000);
example.shutdown();
for (Thread consumer : consumers) consumer.join();
monitor.join();
System.out.println("Final queue: " + example.snapshot());
}
}
5. Performance Considerations
ReadWriteLock vs Synchronized Benchmark
import java.util.concurrent.locks.*;
import java.util.concurrent.*;
public class ReadWriteLockBenchmark {
private final Object syncLock = new Object();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private int value;
private int readCount = 0;
private int writeCount = 0;
// Synchronized version
public int readWithSynchronized() {
synchronized (syncLock) {
readCount++;
return value;
}
}
public void writeWithSynchronized(int newValue) {
synchronized (syncLock) {
writeCount++;
value = newValue;
}
}
// ReadWriteLock version
public int readWithReadWriteLock() {
readLock.lock();
try {
readCount++;
return value;
} finally {
readLock.unlock();
}
}
public void writeWithReadWriteLock(int newValue) {
writeLock.lock();
try {
writeCount++;
value = newValue;
} finally {
writeLock.unlock();
}
}
public void reset() {
synchronized (syncLock) {
readCount = 0;
writeCount = 0;
value = 0;
}
}
public static void main(String[] args) throws InterruptedException {
ReadWriteLockBenchmark benchmark = new ReadWriteLockBenchmark();
int threadCount = 10;
int operationsPerThread = 10000;
double readRatio = 0.9; // 90% reads, 10% writes
System.out.println("Benchmarking with " + threadCount + " threads, " +
operationsPerThread + " operations per thread, " +
(readRatio * 100) + "% reads");
// Test synchronized
long syncTime = runBenchmark(benchmark, threadCount, operationsPerThread, readRatio, true);
int syncReads = benchmark.readCount;
int syncWrites = benchmark.writeCount;
benchmark.reset();
// Test ReadWriteLock
long rwTime = runBenchmark(benchmark, threadCount, operationsPerThread, readRatio, false);
int rwReads = benchmark.readCount;
int rwWrites = benchmark.writeCount;
System.out.println("\nResults:");
System.out.printf("Synchronized: %d ms (reads: %d, writes: %d)%n",
syncTime, syncReads, syncWrites);
System.out.printf("ReadWriteLock: %d ms (reads: %d, writes: %d)%n",
rwTime, rwReads, rwWrites);
System.out.printf("Improvement: %.2fx%n", (double) syncTime / rwTime);
}
private static long runBenchmark(ReadWriteLockBenchmark benchmark,
int threadCount, int operationsPerThread,
double readRatio, boolean useSynchronized)
throws InterruptedException {
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(threadCount);
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
try {
startLatch.await(); // Wait for start signal
Random random = new Random();
for (int j = 0; j < operationsPerThread; j++) {
if (random.nextDouble() < readRatio) {
if (useSynchronized) {
benchmark.readWithSynchronized();
} else {
benchmark.readWithReadWriteLock();
}
} else {
if (useSynchronized) {
benchmark.writeWithSynchronized(random.nextInt());
} else {
benchmark.writeWithReadWriteLock(random.nextInt());
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
endLatch.countDown();
}
});
}
for (Thread thread : threads) thread.start();
long startTime = System.currentTimeMillis();
startLatch.countDown(); // Start all threads
endLatch.await(); // Wait for all threads to complete
long endTime = System.currentTimeMillis();
return endTime - startTime;
}
}
6. Best Practices and Common Pitfalls
Best Practices Example
import java.util.concurrent.locks.*;
public class ReadWriteLockBestPractices {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private String data;
// ✅ GOOD: Always use try-finally
public String safeRead() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}
// ✅ GOOD: Use tryLock with timeout for write operations
public boolean safeWrite(String newData, long timeout, TimeUnit unit) {
try {
if (writeLock.tryLock(timeout, unit)) {
try {
data = newData;
return true;
} finally {
writeLock.unlock();
}
}
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
// ✅ GOOD: Document lock ordering if using multiple locks
public void multipleResourceAccess(String otherData, ReadWriteLockBestPractices other) {
// Always acquire locks in consistent order to prevent deadlocks
boolean locked1 = false;
boolean locked2 = false;
try {
if (this.writeLock.tryLock(100, TimeUnit.MILLISECONDS)) {
locked1 = true;
if (other.writeLock.tryLock(100, TimeUnit.MILLISECONDS)) {
locked2 = true;
// Both locks acquired - perform operation
this.data = otherData;
other.data = this.data;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (locked2) other.writeLock.unlock();
if (locked1) this.writeLock.unlock();
}
}
// ❌ BAD: Don't forget to unlock in all code paths
public String unsafeRead(boolean shouldProcess) {
readLock.lock();
if (shouldProcess) {
// ❌ If shouldProcess is false, lock is never released!
return data.toUpperCase();
}
readLock.unlock(); // ❌ This might not be reached
return data;
}
// ❌ BAD: Don't call external code while holding locks
public void unsafeOperation(String newData) {
writeLock.lock();
try {
data = newData;
externalService.call(data); // ❌ External call while holding lock
} finally {
writeLock.unlock();
}
}
// ✅ GOOD: Release lock before calling external code
public void safeOperation(String newData) {
String dataToSend;
writeLock.lock();
try {
data = newData;
dataToSend = data;
} finally {
writeLock.unlock();
}
// Call external service without holding the lock
externalService.call(dataToSend);
}
// Mock external service for demonstration
private static class ExternalService {
void call(String data) {
// Simulate external service call
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private final ExternalService externalService = new ExternalService();
}
Summary
ReadWriteLock provides significant advantages:
- Performance Boost - Multiple concurrent readers
- Write Exclusion - Single writer with exclusive access
- Flexibility - TryLock, timed operations, conditions
- Fairness Options - Fair or non-fair locking
Key patterns:
- Cache implementations - Read-heavy with occasional updates
- Configuration management - Frequent reads, rare writes
- Resource pooling - Shared resource access
- Data structures - Thread-safe collections
Best practices:
- Always use try-finally for lock/unlock
- Use tryLock with timeouts to avoid deadlocks
- Release locks before calling external code
- Consider lock downgrading for read-heavy post-processing
- Use fair locking when thread starvation is a concern
ReadWriteLock is ideal for read-heavy workloads where data is read frequently but updated infrequently, providing better scalability than synchronized methods or ReentrantLock in these scenarios.