Home Post Multithreading

Concurrency in Java: Reentrant, Read/Write and Stamped Locks

Mar 31, 2024

In Java, locks are a more flexible and sophisticated thread synchronization mechanism than the standard synchronized block.

The "Lock" interface was added in Java 1.5 and is defined inside the java.util.concurrent.lock package along with its several implementations.

You may never need to implement your own locks, but a simple lock can be implemented using "synchronized", "wait()" and "notify()".

We can use this simple lock in one of our previous examples in place of the "synchronized" keyword.

The usage of "while(isLocked)" in place of "if(isLocked)" is an example of a "spin lock."

A "spin lock" is useful in situations when a thread wakes up spuriously without having received a notify() call. In the event of a false wakeup, the thread re-checks the isLocked condition to determine whether it is safe to proceed.

Locks

ReentrantLock

ReentrantLock is a concrete implementation of the "Lock" interface provided in the "java.util.concurrent.lock" package.

1) void lock() - If the lock is available, it is taken. If the lock isn't available, a thread gets blocked until the lock is released.

Lock acquired by Thread-0
Lock acquired by Thread-1
Lock acquired by Thread-2

2) boolean tryLock() – This is a nonblocking version of the lock() method. It immediately attempts to acquire the lock and returns true if it is successful.

Lock could not be acquired by Thread-2
Run by Thread-2
Lock acquired by Thread-0
Lock could not be acquired by Thread-1
Run by Thread-1
Lock to be unlocked by Thread-0
Run by Thread-0

3) boolean tryLock(long timeout, TimeUnit timeUnit) - Similar to tryLock(), but it waits up to a specified timeout before giving up on attempting to acquire the lock.

Lock acquired by Thread-1
Lock to be unlocked by Thread-1
Lock acquired by Thread-0
Run by Thread-1
Lock could not be acquired by Thread-2
Run by Thread-2
Lock to be unlocked by Thread-0
Run by Thread-0

Read / Write Locks

In addition to the "Lock" interface, we have a "ReadWriteLock" interface that maintains a pair of locks, one for read-only operations and one for the write operation.

In Read/Write locks, multiple threads that want to read the resource are granted overlapping access at the same time. But if a single thread wants to write to the resource, no other reads or writes can be in progress at the same time.

ReentrantReadWriteLock

The "ReentrantReadWriteLock" class implements the "java.util.concurrent.lock.ReadWriteLock" interface.

Read Lock - If no thread acquired the write lock or requested it, multiple threads could acquire the read lock.

Write Lock - If no threads are reading or writing, only one thread can acquire the write lock.

Added by: Writer
Added by: Writer
Added by: Writer
Added by: Writer
Added by: Writer
Get by: Reader-1
Get by: Reader-2
Get by: Reader-1
Get by: Reader-2
Get by: Reader-1
Get by: Reader-2
Get by: Reader-1
Get by: Reader-2
Get by: Reader-1
Get by: Reader-2

StampedLock

The StampedLock was introduced in Java 9 and is a capability-based lock with three modes (Writing, Reading and Optimistic Reading) for controlling read/write access.

On acquiring a lock, a stamp (long value) is returned that represents and controls access with respect to the lock state. The stamp can be used later on to release the lock or convert the existing acquired lock to a different mode.

StampedLock is 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).

StampedLocks are serializable but always deserialize into their initial unlocked state, so they are not useful for remote locking.

So if you have a scenario where you have more readers than writers, using a StampedLock can significantly improve performance.

Method tryOptimisticRead() returns a non-zero stamp only if the lock is not currently held in write mode. If the lock has not been acquired in write mode since obtaining a given stamp, the method validate(long) returns true.

Added by: Writer
Added by: Writer
Added by: Writer
Added by: Writer
Added by: Writer
Get by: Thread-3
Get by: Optimistic Reader
Get by: Reader-1
Get by: Reader-1
Get by: Optimistic Reader
Get by: Reader-1
Get by: Optimistic Reader
Get by: Optimistic Reader
Get by: Reader-1
Get by: Optimistic Reader
Get by: Reader-1

Lock vs Synchronized Block

Lock framework works like synchronized blocks except locks can be more sophisticated than Java's synchronized blocks.

1) Locks can be implemented across the methods, you can invoke lock() in one method and invoke unlock() in another method. A synchronized block is fully contained within a method.

2) A thread gets blocked if it can't get access to the synchronized block. On the other hand, the trylock(timeout) method in the Lock framework will get the lock on the resource if it is available; otherwise, it returns false and the thread won't get blocked.

3) Fairness management is not possible with synchronized blocks. We can achieve fairness within the Lock APIs by specifying the fairness property.

4) A list of waiting threads cannot be seen with the synchronized keyword. A list of waiting threads can be seen using the Lock framework.

5) The unlock() method of a lock cannot be executed if the method throws an exception. Synchronization works clearly in this case. It releases the lock.

avatar

NK Chauhan

NK Chauhan is a Principal Software Engineer with one of the biggest E Commerce company in the World.

Chauhan has around 12 Yrs of experience with a focus on JVM based technologies and Big Data.

His hobbies include playing Cricket, Video Games and hanging with friends.

Categories
Spring Framework
Microservices
BigData
Core Java
Java Concurrency