StampedLock was introduced in Java 8 and has been one of the most important features of the concurrent family.
This is to support the Read/Write Lock, as RWLock got introduced way back in Java 5, which is facing severe starvation.
Sometimes, we need better control over synchronization and to achieve this, we need a separate lock for read and write access. Thankfully, Java introduced ReentrantReadWriteLock under the java.util.concurrent.locks package, which provides an explicit locking mechanism.
In ReadWrite Locking policy, it allows the read lock to be held simultaneously by multiple reader threads, as long as there are no writers, and the write lock is exclusive.
But it was later discovered that ReentrantReadWriteLock has some severe issues with starvation if not handled properly (using fairness may help, but it may be an overhead and compromise throughput). For example, a number of reads but very few writes can cause the writer thread to fall into starvation. Make sure to analyze your setup properly to know how many reads/writes are present before choosing ReadWriteLock.
But it's within this ReadWriteLock series that Java introduced StampedLock.
StampedLock is made of a stamp and mode, where your lock acquisition method returns a stamp, which is a long value used for unlocking within the finally block. If the stamp is ever zero, that means there's been a failure to acquire access. StampedLock is all about giving us a possibility to perform optimistic reads.
Keep one thing in mind: StampedLock is not reentrant, so each call to acquire the lock always returns a new stamp and blocks if there's no lock available, even if the same thread already holds a lock, which may lead to deadlock.
Another point to note is that ReadWriteLock has two modes for controlling the read/write access while StampedLock has three modes of access:
Reading: Method 'public long readLock()' actually acquires a non-exclusive lock, and it blocks, if necessary, until available. It returns a stamp that can be used to unlock or convert the mode.
Writing: Method 'public long writeLock()' acquires an exclusive lock, and it blocks, if necessary, until available. It returns a stamp that can be used to unlock or convert the mode.
Optimistic reading: Method 'public long tryOptimisticRead()' acquires a non-exclusive lock without blocking only when it returns a stamp that can be later validated. Otherwise the value is zero if it doesn't acquire a lock. This is to allow read operations. After calling the tryOptimisticRead() method, always check if the stamp is valid using the 'lock.validate(stamp)' method, as the optimistic read lock doesn't prevent another thread from getting a write lock, which will make the optimistic read lock stamp's invalid.
You might be wondering how to convert the mode and when. Well:
There could be a situation when you acquired the write lock and written something and you wanted to read in the same critical section. So, as to not break the potential concurrent access, we can use the 'tryConvertToReadLock(long stamp)' method to acquire read access.
Now suppose you acquired the read lock, and after a successful read, you wanted to change the value. To do so, you need a write lock, which you can acquire using the 'tryConvertToWriteLock(long stamp)' method.
Note: One thing to note is that the tryConvertToReadLock and tryConvertToWriteLock methods will not block and may return the stamp as zero, which means these methods' calls were not successful.
With all that out of the way, let's run through an example of a simple tax office program where totalRevenue is an invariant.