Core Java

Locks In Java

A lock is a thread synchronization mechanism like synchronized blocks. Locks are implemented internally using synchronized blocks. Therefore, we can use locks instead of synchronized keywords in Java. A Lock is more flexible and more sophisticated than a synchronized block.

From Java 5 version, JDK provides several implementations of locks, like ReentrantReadWriteLock, ReentrantLock, and StampedLock, etc.

1. Differences between Synchronization and Locks

1) We can set a timeout to get access to the resources using Lock.tryLock(long timeout, TimeUnit timeUnit) method, whereas it is not possible with synchronization.

2) The synchronized block must be fully contained within a single process. A lock can be contained in two separate processes: lock() and unlock().

3) A thread that is in the “waiting” state to acquire access to the synchronized block can’t be interrupted. The Lock API provides a method lockInterruptible(), which can be used to disrupt the thread when it’s waiting for the Lock.

2. Implementing Simple Lock:

Simple increment functionality is implemented using the synchronized keyword.

public class Counter {

            private int number = 0;

            public void increment() {

                        synchronized(number) {

                                    return ++number;

                        }
            }
}

Let’s convert the above programs, using the Lock interface.

public class Counter {

            private int number = 0;

            private Lock lock = new Lock();

            public void increment() {                   

                        lock.lock();

                        int newNumber  = ++number;

                        lock.unlock();

                        return new number;
            }
}

Here, we are using a lock interface, instead of the synchronized keyword.

Before incrementing the number, we would have to lock, so that no other can enter anything to this block, till the lock is incremented and released.

3. Ways to improve the code

Let’s suppose some threads are reading the data, and some threads are writing dates to some resources. For reading threads, if one thread is reading from resources, if another also reading from the resource, it will not cause any problem, but if one thread is writing to resource, another thread also writes data to resource, it will cause a problem.

  • For reading operations, multiple threads can be allowed to read data from resources, but not to write threads.
  • If one thread requested for reading access, one thread is requested for write access, what will be the priority, which thread will get access to the resources?
  • If one thread is requested for reading access, a second thread is requested for write access, and if more threads requesting for reading access, if we allow only read requested threads, write thread will need to wait an indefinite amount of time, which leads to Starvation.
  • To avoid these types of scenarios, Java sets some rules when we will read access and write access.

3.1 ReadWriteLock

ReadWriteLock is the implementation provided in Java from the Java 5 version; it has two methods, read Lock () and write-lock().

Read Lock method is used for a read operation, whereas, the write-lock() method is used for a write operation.

3.2 Lock Reentrance

Synchronized blocks in Java are reentrant. If a Java thread enters a synchronized block of code, take the Lock on the monitor object where the block is synchronized. Then, the thread can enter other Java code blocks synchronized on the same monitor object.

4. Lets the consider below scenario:

  1. Thread1 is getting read access.
  2. Thread2 is requesting to write access; it will be blocked as there is one reader.
  3. Thread1 again requesting for reading access, it will be blocked as there is one write request.

Thread1 and thread2 both will be blocked, which leads to a deadlock situation.

To make the locks Reentrance, Java provides one more implementation for locks.

While using reentrance locking, we need to understand some use-cases like,

On the same object, in some the method, a thread may request for reading access, on the same object, maybe in a different method, it may request for write access and vice- versa.

4.1 Read Re-entrance:

The thread will be granted Reentrance if the thread can get read access (no write access and no write requests), or it is already has read access. ( regardless of writing requests).

4.2 Write Re-entrance:

Write Reentrance is granted, only if they have to write access.

4.3 Read to write entrance:

Some times, it is necessary for a thread to read and also write access. For this to allow, the thread must be the only reader.

4.4 Write to Read entrance:

Sometimes a thread that has to write access needs read access too. A writer should always be granted read access if requested. If the thread has read access, no other threads can have read nor write access, so it is not dangerous.

Based on the above concept, let’s implement the synchronized version of ArrayList.

ThreadSaftyArrayList.java

public class ThreadSaftyArrayList {

       private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

       private final Lock writeLock = readWriteLock.writeLock();

       private final Lock readLock = readWriteLock.readLock();

       private List list = new ArrayList();

       public E get(int index)

       {

              readLock.lock();

              try {               

                     return list.get(index);

              } finally {

                     readLock.unlock();

              }

       }

       public void set(E e)

       {

              writeLock.lock();

              try {

                     list.add(e);

              } finally {

                     writeLock.unlock();

              }

       }

       public static void main(String[] args)

    {

              ThreadSaftyArrayList threadSafeArrayList = new ThreadSaftyArrayList();

         threadSafeArrayList.set("1");

         threadSafeArrayList.set("2");

         threadSafeArrayList.set("3");

        System.out.println("Printing the First Element : "+threadSafeArrayList.get(1));

    }
}

Note: Calling unlocks () From a finally-clause:

When guarding a critical section with a ReadWriteLock and the critical section may throw exceptions, it is important to call the readUnlock() and writeUnlock() methods from inside a finally-clause. Doing so makes sure that the ReadWriteLock is unlocked so other threads can lock it.

Pseudocode:

lock.lockWrite();

try{

  //do critical section code, which may throw an exception

} finally {

  lock.unlockWrite();

}

5. Conclusion

In the current blog, we learned about locks, how to implement locks in Java, replacing the synchronized blocks. We also learned about different types of locks, like read Lock and write-lock. We learned about Starvation, when it would occur, and also about Reentrance locks, what are its uses, and implementation classes provided in Java. Finally, we wrote the sample ThreadSafty Arraylist class using the ReentrantReadWriteLock class. I hope the above tutorial is helpful to Java developers and the community.

6. Download the Locks In Java

This was an example of the Locks in Java.

Download
You can download the full source code of this example here: Locks In Java

Vipul Patel

Vipul Patel is a Senior Java Technical Lead working at Aegis Software Pvt Ltd. Vipul is having 10+ years of experience in Java Development, having strong hands-on Spring, Spring Boot, EJB, Hibernate & Apache Spark. Vipul manages Java Practices at Aegis Group that includes Projects from Multinational clients across the world.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button