Introduced in Java 8, the StampedLock in Java is a powerful concurrency utility designed to improve performance in multi-threaded applications. Unlike the traditional ReentrantReadWriteLock in Java, the StampedLock not only provides distinct read and write locks, but also supports optimistic locking for read operations, allowing threads to read data without blocking, as long as no write occurs concurrently.
Another key advantage of StampedLock in Java is its ability to upgrade a read lock to a write lock, a feature missing in ReentrantReadWriteLock. This makes it especially useful in scenarios where a read operation may need to transition into a write operation seamlessly.
Every locking method in StampedLock returns a stamp (a long value) that uniquely identifies the lock state. These stamps are then used to release locks, validate lock states, or convert between lock modes.
Here’s a simple example of acquiring and releasing a write lock using StampedLock in Java-
StampedLock sl = new StampedLock();
//acquiring writelock
long stamp = sl.writeLock();
try{
...
...
}finally {
//releasing lock
sl.unlockWrite(stamp);
}
Modes in Java StampedLock
The StampedLock in Java provides three distinct modes of locking: write mode, read mode, and the unique optimistic read mode. Each mode is designed to balance concurrency and performance in multi-threaded applications.
1. Writing Mode
- Acquired using the writeLock() method.
- This lock is exclusive, meaning no other read or write locks can be held simultaneously.
- The method returns a stamp (long value) that must be used with unlockWrite(long stamp) to release the lock or convert it to another mode.
- Non-blocking alternatives like tryWriteLock() (untimed) and tryWriteLock(long time, TimeUnit unit) (timed) are available.
- If the lock is unavailable, these methods return 0 as the stamp.
- When a write lock is held, all read locks are blocked and optimistic reads fail validation.
2. Reading Mode
- Acquired using the readLock() method.
- This lock is shared, allowing multiple readers to hold the lock simultaneously, as long as no write lock is active.
- Returns a stamp that can be used to release the lock (unlockRead(long stamp)) or convert it.
- Non-blocking versions tryReadLock() and tryReadLock(long time, TimeUnit unit) are also available.
- If the lock is not immediately available, these methods return 0.
3. Optimistic Reading Mode
- A unique feature of StampedLock in Java, acquired using tryOptimisticRead().
- Returns a non-zero stamp if no write lock is currently held.
- This allows threads to read data without blocking, improving throughput in read-heavy scenarios.
- Since optimistic reads are not guaranteed to be valid, the validate(long stamp) method must be used to check if the data read is still consistent.
- validate() returns true if no write lock has been acquired since the stamp was issued.
Lock conversion in StampedLock
StampedLock class in Java also supports methods that conditionally provide conversions across the three modes. The forms of these methods are designed to help reduce some of the code bloat that otherwise occurs in retry-based designs.
For example, method tryConvertToWriteLock(long)attempts to "upgrade" a mode, returning a valid write stamp if
(1) already in writing mode (2) in reading mode and there are no other readers or (3) in optimistic mode and the lock is available.
- tryConvertToWriteLock(long stamp)- If the lock state matches the given stamp, performs one of the following actions. If the stamp represents holding a write lock, returns it. Or, if a read lock, if the write lock is available, releases the read lock and returns a write stamp. Or, if an optimistic read, returns a write stamp only if immediately available. This method returns zero in all other cases.
- tryConvertToReadLock(long stamp)- If the lock state matches the given stamp, performs one of the following actions. If the stamp represents holding a write lock, releases it and obtains a read lock. Or, if a read lock, returns it. Or, if an optimistic read, acquires a read lock and returns a read stamp only if immediately available. This method returns zero in all other cases.
- tryConvertToOptimisticRead(long stamp)- If the lock state matches the given stamp then, if the stamp represents holding a lock, releases it and returns an observation stamp. Or, if an optimistic read, returns it if validated. This method returns zero in all other cases, and so may be useful as a form of "tryUnlock".
Features of StampedLock
1. The scheduling policy of StampedLock does not consistently prefer readers over writers or vice versa so there is no acquisition preference. All "try" methods are best-effort and do not necessarily conform to any scheduling or fairness policy.
2. Unlike ReentrantLocks, StampedLocks are not reentrant, so locked bodies should not call other unknown methods that may try to re-acquire locks (although you may pass a stamp to other methods that can use or convert it).
StampedLock Java examples
Let’s see some examples in order to get a better understanding of the StampedLock.
1. Using the read/write lock
This example uses the write lock in order to get an exclusive lock for a counter and there is also a read lock which tries to read the value of the counter.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.StampedLock;
public class CounterST {
int c = 0;
public static void main(String[] args) {
StampedLock sl = new StampedLock();
ExecutorService executor = Executors.newFixedThreadPool(2);
CounterST cst = new CounterST();
// Runnable as lambda - read
Runnable readTask = ()->{
long stamp = sl.readLock();
try{
System.out.println("value " + cst.getValue());
}finally{
sl.unlockRead(stamp);
}
};
// Runnable as lambda - Write lock
Runnable writeTask = ()->{
long stamp = sl.writeLock();
try {
cst.increment();
}finally{
sl.unlockWrite(stamp);
}
};
// 3 write tasks
executor.submit(writeTask);
executor.submit(writeTask);
executor.submit(writeTask);
// 1 read task
executor.submit(readTask);
executor.shutdown();
}
public void increment() {
c++;
System.out.println("in increment " + c);
}
public int getValue() {
return c;
}
}
Output
in increment 1 in increment 2 value 2 in increment 3
2. Using tryOptimisticRead method - StampedLock example
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
public class StampedLockDemo {
public static void main(String[] args) {
StampedLock sl = new StampedLock();
ExecutorService executor = Executors.newFixedThreadPool(2);
// Runnable as lambda - optimistic read
Runnable r1 = ()->{
long stamp = sl.tryOptimisticRead();
try{
System.out.println("In optimistic lock " + sl.validate(stamp));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("In optimistic lock " + sl.validate(stamp));
}finally{
sl.unlock(stamp);
}
};
// Runnable as lambda - Write lock
Runnable r2 = ()->{
System.out.println("about to get write lock");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long stamp = sl.writeLock();
try{
System.out.println("After getting write lock ");
}finally{
sl.unlock(stamp);
System.out.println("Relinquished write lock ");
}
};
executor.submit(r2);
// Optimistic read
executor.submit(r1);
executor.submit(r2);
executor.shutdown();
}
}
Output
about to get write lock In optimistic lock true After getting write lock Relinquished write lock about to get write lock In optimistic lock false After getting write lock Relinquished write lock
Here it can be seen when write lock is acquired at that time validate method returns false for optimistic read. Optimistic mode can be thought of as an extremely weak version of a read-lock, that can be broken by a writer at any time, from the output you can see the same thing.
Before acquiring the write lock thread goes to sleep and the optimistic lock is acquired in the mean time. When the write lock is acquired later, optimistic read lock is broken so the validate method returns false for the same stamp.
Since optimistic read mode is a new feature provided by StampedLock so let’s see one more example which is provided in Javadocs.
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
In this method you can notice that initially values are assigned to variables currentX and currentY after getting the optimistic lock. Then validate method is used to check if the lock has not been exclusively acquired since issuance of the given stamp. In case validate method returns false (which means write lock is acquired by some thread after optimistic lock is acquired) then read lock method is acquired and values are assigned again. Here note that read lock may block if there is any write lock. So that is the benefit of optimistic lock you can acquire it and read the values and then check if there is any change in the values, if there is then only you need to go through the blocking read lock.
3. StampedLock Example using tryConvertToWriteLock method
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
}
else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
Here it can be seen that initially read lock is acquired and then some condition is checked if it satisfies then only an attempt is made to convert the read lock to write lock. If returned stamp is not zero that means conversion was successful otherwise go through the procedure of releasing the read lock and acquiring a write lock.
That's all for this topic Java StampedLock With Examples. If you have any doubt or any suggestions to make please drop a comment. Thanks!
Related Topics
You may also like-
Consider that the readlock is held by a thread (while no other threads are holding the readlock) and some thread tries to convert to writeLock ... the conversion succeeds. Now, what if the thread that held the readLock reads value of some variables while it continues its processing.... will that thread get stale value ? The point is that the thread that got the conversion successful will certainly write to the shared object and the thread that held readLock may read the stale value while continuing its processing. Of course Doug Lea is smarted than anyone on earth and the implementation of StampedLock would some how take care of this scenario... however, I am curious to know ... how !
ReplyDeleteWrite lock always has exclusive access .. When the lock is held in write mode, no read locks may be obtained, and all optimistic read validations will fail. That holds true for the tryConvertToWriteLock(long stamp) method also ..
Delete