The StampedLock
class in Java’s concurrency utilities (introduced in Java 8) is a high-performance read/write lock that differs from traditional ReadWriteLock
(like ReentrantReadWriteLock
) due to its ability to provide three locking modes:
- Write Lock: Exclusive access.
- Read Lock: Shared (non-exclusive) access.
- Optimistic Read Lock: A lightweight, non-blocking read lock for scenarios where reads dominate writes, but data consistency needs to be validated.
Below is an explanation of how to use StampedLock
effectively for high-performance locking in different contexts:
1. Write Lock
The write lock is used when exclusive access to the shared resource is required, such as for updates. It provides behavior similar to a traditional lock but with better performance in many scenarios.
Example:
package org.kodejava.util.concurrent;
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private int count = 0;
private final StampedLock lock = new StampedLock();
public void increment() {
long stamp = lock.writeLock(); // Acquire write lock
try {
count++;
} finally {
lock.unlockWrite(stamp); // Release write lock
}
}
}
2. Read Lock
The read lock is used when shared access to a resource is sufficient, and there are no write operations being performed. It provides better throughput than a traditional lock by allowing multiple threads to read concurrently.
Example:
public int getCount() {
long stamp = lock.readLock(); // Acquire read lock
try {
return count;
} finally {
lock.unlockRead(stamp); // Release read lock
}
}
3. Optimistic Read Lock
The optimistic read lock is a key feature of StampedLock
and is designed for scenarios where reads dominate and writes are infrequent. This mode allows a thread to proceed without actually acquiring a lock, provided that the shared resource isn’t later invalidated by a write operation.
Process:
- Acquire an optimistic read stamp with
lock.tryOptimisticRead()
. - Perform the read operation.
- Validate the stamp with
lock.validate(stamp)
. If the stamp is no longer valid (i.e., a write operation occurred), fall back to a read lock.
Example:
public int optimisticReadCount() {
long stamp = lock.tryOptimisticRead(); // Try optimistic read
int currentCount = count; // Perform read operation
if (!lock.validate(stamp)) { // Check if stamp is still valid
// Fallback to read lock if a write occurred during the read
stamp = lock.readLock();
try {
currentCount = count;
} finally {
lock.unlockRead(stamp);
}
}
return currentCount; // Return the read value
}
This approach is highly efficient in scenarios with minimal contention, as it avoids actual locking unless necessary.
4. Combining Reads and Writes
Sometimes, a single operation involves both reads and writes. In such cases, you can upgrade from a read lock to a write lock using the convertToWriteLock
method:
Example:
public void conditionalIncrement() {
long stamp = lock.readLock();
try {
if (count < 10) {
// Upgrade to write lock if modification is needed
stamp = lock.tryConvertToWriteLock(stamp);
if (stamp == 0L) { // Failed to upgrade, acquire write lock normally
stamp = lock.writeLock();
}
count++;
}
} finally {
lock.unlock(stamp); // Release the appropriate lock
}
}
5. Things to Keep in Mind
- Deadlock Avoidance:
StampedLock
does not support reentrancy (unlikeReentrantLock
). Each thread must acquire the lock only once; otherwise, it may lead to deadlocks. - Performance: Using optimistic reads can offer great performance improvements in read-heavy scenarios, but they require careful validation to ensure correctness.
- Fairness:
StampedLock
is not fair and does not guarantee lock acquisition order.
When to Use StampedLock
- Optimistic Reads: If your application has far more reads than writes, and contention is generally low.
- Shared Data Structures: For use cases like caching or maintaining shared state across threads, where high throughput is critical.
StampedLock
strikes a balance between ease of use and performance, making it a valuable tool for high-performance concurrency tasks in Java!