What is StampedLock in Java? What is StampedLock in Java? multithreading multithreading

What is StampedLock in Java?


StampedLock is an alternative to using a ReadWriteLock (implemented by ReentrantReadWriteLock). The main differences between StampedLock and ReentrantReadWriteLock are that:

  • StampedLocks allow optimistic locking for read operations
  • ReentrantLocks are reentrant (StampedLocks are not)

So if you have a scenario where you have contention (otherwise you may as well use synchronized or a simple Lock) and more readers than writers, using a StampedLock can significantly improve performance.

However you should measure the performance based on your specific use case before jumping to conclusions.

Heinz Kabutz has written about StampedLocks in his newsletter and he also made a presentation about performance.


The API documentation for java.util.concurrent.locks.StampedLock says:

StampedLocks are designed for use as internal utilities in the development of thread-safe components. Their use relies on knowledge of the internal properties of the data, objects, and methods they are protecting. They 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). The use of read lock modes relies on the associated code sections being side-effect-free. Unvalidated optimistic read sections cannot call methods that are not known to tolerate potential inconsistencies. Stamps use finite representations, and are not cryptographically secure (i.e., a valid stamp may be guessable). Stamp values may recycle after (no sooner than) one year of continuous operation. A stamp held without use or validation for longer than this period may fail to validate correctly. StampedLocks are serializable, but always deserialize into initial unlocked state, so they are not useful for remote locking.

e.g. -

 class Point {   private double x, y;   private final StampedLock sl = new StampedLock();   void move(double deltaX, double deltaY) { // an exclusively locked method     long stamp = sl.writeLock();     try {       x += deltaX;       y += deltaY;     } finally {       sl.unlockWrite(stamp);     }   }   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);   }   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);     }   } }


StampedLock support read and write locks. In contrast to ReadWriteLock the locking methods of a StampedLock return a stamp represented by a long value. You can use these stamps to either release a lock or to check if the lock is still valid. Additionally stamped locks support another lock mode called optimistic locking.

ExecutorService executor = Executors.newFixedThreadPool(2);        Map<String, String> map = new HashMap<>();        StampedLock lock = new StampedLock();        executor.submit(() -> {            long stamp = lock.writeLock();            try {                Thread.sleep(100);                map.put("test", "INDIA");            } catch (Exception e) {            } finally {                lock.unlockWrite(stamp);            }        });        Runnable readTask = () -> {            long stamp = lock.readLock();            try {                System.out.println(map.get("test"));                Thread.sleep(100);            } catch (Exception e) {            } finally {                lock.unlockRead(stamp);            }        };        executor.submit(readTask);        executor.submit(readTask);

Obtaining a read or write lock via readLock() or writeLock() returns a stamp which is later used for unlocking within the finally block. Keep in mind that stamped locks don't implement reentrant characteristics. Each call to lock returns a new stamp and blocks if no lock is available even if the same thread already holds a lock. So you have to pay particular attention not to run into deadlocks.

executor.submit(() -> {            long stamp = lock.tryOptimisticRead();            try {                System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));                Thread.sleep(100);                System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));                Thread.sleep(1000);                System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));            } catch (Exception e) {            } finally {                lock.unlock(stamp);            }        });        executor.submit(() -> {            long stamp = lock.writeLock();            try {                System.out.println("Write Lock acquired");                Thread.sleep(100);            } catch (Exception e) {            } finally {                lock.unlock(stamp);                System.out.println("Write done");            }        });

An optimistic read lock is acquired by calling tryOptimisticRead() which always returns a stamp without blocking the current thread, no matter if the lock is actually available. If there's already a write lock active the returned stamp equals zero. You can always check if a stamp is valid by calling lock.validate(stamp).