Skip to main content

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

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.

Comments

Popular posts from this blog

Deploying Spring Boot microservices on Kubernetes Cluster

This article guides you through the deployment of two Spring Boot microservices, namely "order-service" and "inventory-service," on Kubernetes using "MiniKube" . We will establish communication between them, with "order-service" making calls to an endpoint in "inventory-service." Additionally, we will configure "order-service" to be accessible from the local machine's browser . 1) Create Spring Boot microservices The Spring Boot microservices, "order-service" and "inventory-service," have been developed and can be found in this GitHub repository. If you are interested in learning more about creating Spring Boot REST microservices, please refer to this or this (Reactive) link. 2) Build Docker Images The Docker images for both "order-service" and "inventory-service" have already been generated and deployed on DockerHub, as shown below. codeburps/order-service cod...

Circuit Breaker Pattern with Resilience4J in a Spring Boot Application

Read Also: Spring Cloud Circuit Breaker + Resilience4j Resilience4j is a lightweight fault tolerance library that draws inspiration from Netflix Hystrix but is specifically crafted for functional programming. The library offers higher-order functions, known as decorators , designed to augment any functional interface, lambda expression, or method reference with features such as Circuit Breaker, Rate Limiter, Retry, or Bulkhead . These functionalities can be seamlessly integrated within a project, class, or even applied to a single method. It's possible to layer multiple decorators on any functional interface, lambda expression, or method reference, allowing for versatile and customizable fault tolerance. While numerous annotation-based implementations exist online, this article focuses solely on the reactive approach using router predicates and router functions . How Circuit Breaker Pattern works? In general, a circuit breaker functions as an automatic electrical s...

How to create a basic Spring 6 project using Maven

Below is a step-by-step guide to creating a basic Spring project using Maven. 1) Create a Maven Project Use the following Maven command to create a new Maven project. mvn archetype:generate -DgroupId=com.tb -DartifactId=spring-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false 2) Import in IntelliJ IDEA If you haven't already, open IntelliJ IDEA on your system. Go to "File" > "New" > "Project from Existing Sources..." . In the file dialog, navigate to the directory where your Maven project is located. Select the pom.xml file within the project directory and click "Open." 3) Update pom.xml In total, the application requires the below-mentioned dependencies: 4) Create Spring Configuration Create a Java configuration class that uses annotations to define your Spring beans and their dependencies. This class should be annotated with @Configuration . 5) Create the Main Application C...