Locks In Java — Part 6.2 [ Stamped Lock Conversions ]
In this article we are going to take forward the concepts discussed in the previous part :
now we will discuss about the conversions of lock into one another which is supported by stamped locks
lets see them one by one
TryConvertToWriteLock
this method takes input a stamp which represents a lock , and returns a stamp if the conversion is successful otherwise it returns 0 to indicate a failure.
/*
Consider an App Like Swiggy where the user is trying to go through
the menu and wants to have the read access
then it makes up it mind and then wants to go and make a
purchase ( write).
so here using the stamped lock we can try to convert the
same read lock to convert into the write lock considering
the condition that
if the stamped lock is read lock and the write lock is available
then we release the read lock and return the write stamp
otherwise if the stamped lock is a optimistic read then
we return the write stamp only if immediately available
*/
public class TryConvertToWriteExample {
private final StampedLock lock = new StampedLock();
int availableBurgers = 4;
public int checkForAvailability(){
long readStamp = lock.readLock();
System.out.println("read lock acquired for
checking availability");
try{
return availableBurgers;
}
finally {
lock.unlockRead(readStamp);
System.out.println("read lock acquired for
checking availability released");
}
}
public void book(int qty){
long stamp = lock.readLock();
System.out.println("read lock acquired for booking");
try{
if((availableBurgers - qty) > 0){
long writeStamp = lock.tryConvertToWriteLock(stamp);
System.out.println("conversion of lock tried ");
if(writeStamp!=0){
// this means the lock is now converted
System.out.println("lock conversion successfull");
stamp = writeStamp;
availableBurgers-=qty;
System.out.println("Burgers Booked");
}
else {
// not able to convert
// so now we will try to do it by
// acquiring the write lock
long wStamp = lock.writeLock();
System.out.println("write lock acquired exclusively ");
if((availableBurgers - qty) > 0){
availableBurgers-=qty;
stamp = wStamp;
System.out.println("burgers Booked");
}
else{
System.out.println("booking failed !!");
}
}
}
else{
System.out.println("Burger Out of Stock!!");
}
}finally {
lock.unlock(stamp);
System.out.println("lock released finally");
}
}
public static void main(String[] args) {
TryConvertToWriteExample example = new TryConvertToWriteExample();
example.checkForAvailability();
example.book(2);
}
}
output :
now lets consider a multithreaded scenario where multiple people are trying to book.
package com.example.multithreading.stampedLock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.StampedLock;
public class TryConvertToWriteMultiThreaded {
private final StampedLock lock = new StampedLock();
int availableBurgers = 4;
public int checkForAvailability(){
long readStamp = lock.readLock();
System.out.println("read lock acquired for checking availability");
try{
return availableBurgers;
}
finally {
lock.unlockRead(readStamp);
System.out.println("read lock acquired for checking
availability released");
}
}
public void book(int qty,String userName){
long stamp = lock.readLock();
System.out.println("[" + userName + "]" + "read lock acquired
for booking");
try{
if((availableBurgers - qty) > 0){
long writeStamp = lock.tryConvertToWriteLock(stamp);
System.out.println("[" + userName + "]" +"conversion of lock tried ");
if(writeStamp!=0){
// this means the lock is now converted
System.out.println("[" + userName + "]" +"lock conversion successfull");
stamp = writeStamp;
availableBurgers-=qty;
System.out.println("[" + userName + "]" +"Burgers Booked");
}
else {
// not able to convert
// so now we will try to do it by acquiring the write lock
lock.unlockRead(stamp);
long wStamp = lock.writeLock();
try{
System.out.println("[" + userName + "]" +"write
lock acquired exclusively ");
if((availableBurgers - qty) > 0){
availableBurgers-=qty;
stamp = wStamp;
System.out.println("[" + userName + "]" +"burgers
Booked");
}
else{
System.out.println("[" + userName + "]" +"booking
failed !!");
}
}finally {
lock.unlockWrite(stamp);
}
}
}
else{
System.out.println("[" + userName + "]" +"Burger
Out of Stock!!");
}
}finally {
lock.unlock(stamp);
System.out.println("[" + userName + "]" +"lock
released finally");
}
}
public static void main(String[] args) {
TryConvertToWriteMultiThreaded mExample = new TryConvertToWriteMultiThreaded();
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i = 1 ; i <= 3 ; ++i){
int finalI = i;
executorService.submit(() -> mExample.book(2,"User" + finalI));
}
executorService.shutdown();
}
}
output :
here eventually the lock conversion didn't happen and it had to go with the normal write lock acquire.
now one question can come in mind that what are the advantages of converting a read / optimistic read lock to a write lock rather than getting the write lock itself.
lets see this ,
- Reduction in Contention : as we convert the lock the thread doesn’t hold the write lock for long time , this helps in reduction in the contention for the lock.
- Performance : converting a read lock to write lock is faster than releasing a read lock and then acquiring the write lock again.
- Code Logic is More Clear and Readable
lets learn about the tryConvertToRead
tryConvertToRead
package com.example.multithreading.stampedLock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.StampedLock;
public class TryConvertToReadLockExample {
private StampedLock lock = new StampedLock();
private int burgerStock;
public TryConvertToReadLockExample (int initialStock){
this.burgerStock = initialStock;
}
public void updateQty(String threadName , int qty){
// first acquire the writeLock
long stamp = lock.writeLock();
System.out.println("[" + threadName + "]" + "Current Qty = " + burgerStock);
try {
burgerStock+=qty;
System.out.println("[" + threadName + "]" + "Qty Updated " + "Updated Qty = " + burgerStock );
// now lets try to convert the write lock to the read lock
long rstamp = lock.tryConvertToReadLock(stamp);
if(rstamp!=0){
// conversion is successfully
stamp = rstamp;
System.out.println("[" + threadName + "]" + "Lock is successfully Converted" );
}
else {
// failed to convert
// create the read lock explicitly
// first we have to release the write lock
lock.unlockWrite(stamp);
stamp = lock.readLock();
}
System.out.println("[" + threadName + "]" + "Updated Qty is = " + burgerStock);
}
finally {
lock.unlock(stamp);
}
}
public static void main(String[] args) {
TryConvertToReadLockExample example = new TryConvertToReadLockExample(10);
ExecutorService service = Executors.newFixedThreadPool(5);
for(int i = 1 ; i <= 5 ; ++i){
int finalI = i;
service.submit(() -> example.updateQty("user" + finalI, finalI * 10));
}
service.shutdown();
}
}
output :
here we can see how at realtime writes are done and sequential reads are done once updated by converting locks into read locks successfully .
lets learn about tryConvertToOptimisticRead:
tryConvertToOptimisticRead
here similar example to how we did with convert to read lock can be taken the only advantage here would be of optimistic reads ( already discussed in the previous part)
package com.example.multithreading.stampedLock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.StampedLock;
public class TryConvertToOptimisticReadExample {
private StampedLock lock = new StampedLock();
private int burgerStock;
public TryConvertToOptimisticReadExample (int initialStock){
this.burgerStock = initialStock;
}
public void updateQty(String threadName , int qty){
// first acquire the writeLock
long stamp = lock.writeLock();
System.out.println("[" + threadName + "]" + "Current Qty = " + burgerStock);
try {
burgerStock+=qty;
System.out.println("[" + threadName + "]" + "Qty Updated " + "Updated Qty = " + burgerStock );
// now lets try to convert the write lock to the optimistic read lock
long orstamp = lock.tryConvertToOptimisticRead(stamp);
if(orstamp!=0){
// conversion is successfully
stamp = orstamp;
System.out.println("[" + threadName + "]" + "Lock is successfully Converted" );
}
else {
// failed to convert
// release the write lock
lock.unlockWrite(stamp);
}
System.out.println("[" + threadName + "]" + "Updated Qty is = " + burgerStock);
}
finally {
// first validate then release
if(!lock.validate(stamp)){
lock.unlock(stamp);
}
}
}
public static void main(String[] args) {
TryConvertToOptimisticReadExample example = new TryConvertToOptimisticReadExample(10);
ExecutorService service = Executors.newFixedThreadPool(5);
for(int i = 1 ; i <= 5 ; ++i){
int finalI = i;
service.submit(() -> example.updateQty("user" + finalI, finalI * 10));
}
service.shutdown();
}
}
‘
output :
here we utilize the advantages of optimistic reads when we are able to successfully convert the lock .
this completes the conversions in stamped locks.
code : https://github.com/avinashsoni9829/Threading/tree/main/locks/stampedLock
lets move further and learn about the remaining basic methods provided by the stamped lock and the ReadView / WriteView in the next part .
thanks for reading !! 😎