Sunday, September 17, 2023

How to use Lock in Java? ReentrantLock Example Tutorial

Many Java programmers confused themselves like hell while writing multi-threaded Java programs like where to synchronized? Which Lock to use? What Lock to use etc. I often receive a request to explain how to use Locks and ReentrantLock in Java, so I thought to write a simple Java program, which is multi-threaded and uses a rather new Lock interface and ReentrantLock to lock critical section. Remember Lock is your tool to guard shared resources which can be anything like a database, File system, a Prime number Generator, or a Message processor. Before using Locks in the Java program, it’s also better to learn some basics. Lock is an interface from java.util.concurrent package. It was introduced in JDK 1.5 release as an alternative to the synchronized keyword.

If you have never written any multi-threading program, then I suggest the first start with the synchronized keyword because it’s easier to use them. Once you are familiar with working of a multi-threading program like How threads share data, how inter-thread communication works, you can start with the Lock facility.

As I told you Lock is an interface, so we cannot use it directly, instead, we need to use its implementation class.

Thankfully Java comes with two implementations of java.util.concurrent.locks.Lock interface, ReentrantLock and ReentrantReadWriteLock, later provides two more inner implementations known as ReentrantReadWriteLock.ReadLock and ReentrantReadWriteLock.WriteLock. For our simple multi-threaded Java program's purpose ReentrantLock is enough.

And, if you are serious about mastering Java multi-threading and concurrency then I also suggest you take a look at the Java Multithreading, Concurrency, and Performance Optimization course by Michael Pogrebinsky on Udemy. It's an advanced course to become an expert in Multithreading, concurrency, and Parallel programming in Java with a strong emphasis on high performance





How to use Lock in a Concurrent Java Application?

Here is the idiom to use Locks in Java :
Lock l = ...;
l.lock();

try {
    // access the resource protected by this lock

} finally {
   l.unlock();
}
You can see that Lock is used to protect a resource so that only one thread can access it at a time. Why do we do that? to make sure our application behaves properly.

For example, we can use Lock to protect a counter, whose sole purpose is to return a count incremented by one, when anyone calls its getCount() method. If we don't protect them by parallel access of thread, then it’s possible that two thread receives the same count, which is against the program's policies.

Now, coming back to semantics, we have used the lock() method to acquire the lock and unlock() method to release the lock.

Always remember to release lock in finally block, because every object has only one lock and if a thread doesn't release it then no one can get it, which may result in your program hung or threads going into deadlock.

That's why I said that a synchronized keyword is simpler than a lock because Java itself makes sure that the lock acquired by a thread by entering into a synchronized block or method is released as soon as it came out of the block or method. 

This happens even if the thread came out by throwing an exception, this is also we have unlock code in finally block, to make sure it run even if the try block throws an exception or not.

In the next section, we will see an example of our multi-threaded Java program, which uses Lock to protect shared Counter.




Java Lock and ReentrantLock Example

Here is a sample Java program, which uses both Lock and ReentrantLock to protect a shared resource. In our case, it’s an object, a counter's object. Invariant of Counter class is to return a count incremented by 1 each time someone calls getCount() method.

Here for testing three threads will call getCount() method simultaneously but the guard provided by Lock will prevent the shared counter. As an exercise, you can also implement the same class using synchronized keywords.

Here is the complete code :


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 * Java Program to show how to use Locks in multi-threading 
 * e.g. ReentrantLock, ReentrantReadWriteLock etc.
 *
 * @author Javin Paul
 */
public class LockDemo {

    public static void main(String args[]) {

        // Let's create a counter and shared it between three threads
        // Since Counter needs a lock to protect its getCount() method
        // we are giving it a ReentrantLock.
        final Counter myCounter = new Counter(new ReentrantLock());

        // Task to be executed by each thread
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.printf("Count at thread %s is %d %n",
                        Thread.currentThread().getName(), myCounter.getCount());
            }
        };

        // Creating three threads
        Thread t1 = new Thread(r, "T1");
        Thread t2 = new Thread(r, "T2");
        Thread t3 = new Thread(r, "T3");

        //starting all threads
        t1.start();
        t2.start();
        t3.start();
    }
}


class Counter {
    private Lock lock; // Lock to protect our counter
    private int count; // Integer to hold count

    public Counter(Lock myLock) {
        this.lock = myLock;
    }

    public final int getCount() {
        lock.lock();
        try {
            count++;
            return count;
        } finally {
            lock.unlock();
        }
        
    }
}
Output:
Count at thread T1 is 1
Count at thread T2 is 2
Count at thread T3 is 3


You can even put a long loop inside Runnable's run() method to call getCount() numerous times if you see a duplicate means there is a problem with your code, but without any duplicate means it’s working fine.

How to use Lock and ReentrantLock in Java with Example




Common Mistakes made by beginners while using Locks in Java

Here are some of the common mistakes I have observed by looking at Java beginners lock-related code :

1. Instead of sharing a lock, they provide different locks to each thread. This often happens to them unknowingly because they usually put the lock and guarded block inside Runnable, and they pass two different instances of Runnable to two different threads like where SimpleLock is a Runnable, as shown below :
Thread firstThread = new Thread(new SimpleLock());
Thread secondThread = new Thread(new SimpleLock());

class SimpleLock implements Runnable {
        private Lock myLock = new ReentrantLock();
        public void printOutput() {
            System.out.println("Hello!");
        }
        public void run() {
            if (myLock.tryLock()) {
                myLock.lock();
                printOutput();
            }else
                System.out.println("The lock is not accessible.");
        }
    }

Since here myLock is an instance variable, each instance of SimpleLock has its own myLock instance, which means firstThread and secondThread are using different locks and they can run protected code simultaneously.

2. Second mistake Java beginners do is forget to call unlock() method, just like the above example. without calling unlock() method, Thread will not release its lock and another thread waiting for that lock will never get that. 

Nothing will happen in this test program, but once you write this kind of code in a real application, you will see nasty issues like deadlock, starvation, and data corruption.  By the way Lock interface also offers several advantages over synchronized keywords, check here to learn more.


That's all about how to use Locks in a multi-threaded Java program for synchronization. Let me know if you have any difficulty understanding Locks in Java or anything related to multi-threading, Will be glad to help you. For further reading, you can explore Java documentation of the Lock interface and its various implementation classes

Other Java Multi-threading Interview Questions you may like to explore
  • Top 50 Java Thread Interview Questions with Answers (list)
  • 6 Books to learn Multithreading and Concurrency in Java (books)
  • How volatile variable works in Java? (answer)
  • Top 5 Courses to learn Multithreading and Concurrency in Java (courses)
  • Top 12 Java Concurrency Questions for Experienced Programmers (see here)
  • Top 5 Books to learn Java Concurrency in-depth (books)
  • Top 10 Java Concurrency and multi-threading best practices (article)
  • Difference between start() and run() method in Java? (answer)
  • Top 10 Courses to learn Java in-depth (courses)
  • 10 Courses to Crack Java Interviews for Beginners (courses)
  • Top 5 courses to Learn Java Performance Tuning (courses)
  • What is happens-before in Java Concurrency? (answer)
  • Top 15 Multithreading and Concurrency Questions from Investment banks (see here)
  • 133 Core Java Interview Questions from the last 5 years (see here)
  • 10 Advanced Core Java Courses for Experienced programmers (course)

Thanks for reading this article. If you like this Java Lock and ReentrantLock tutorial and example then please share it with your friends and colleagues. If you have any suggestions or feedback then please drop a note, I would be happy to clarify any doubts. 

P. S. - If you are a junior Java developer and want to learn multithreading and concurrency and looking for some free courses to start with then you can also check out this free Java Multithreading course on Udemy. It is a good free course to learn  Java Concurrency as well.

Now, over to you, what is difference between ReentrantLock and ReadWriteLock in Java? Which is better to use synchronized keyword or Lock interface?

13 comments :

Anonymous said...

Sorry, but your Counter.getCount is wrong.
Imagine there is some delay after "finally" end and before "return".
Then all your threads will get "3" as an ID.
Here is the code which demonstrates the error:

public final int getCount() {

lock.lock();
try {
count++;
} finally {
lock.unlock();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return count;
}


SARAL SAXENA said...

Hi Javin ,
Persoanlly between lock & syncrnoization . i will say...

if you're simply locking an object, I'd prefer to use synchronized

Example :
Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!
You have to explicitly do try{} finally{} everywhere.

Whereas with synchronized, it's super clear and impossible to get wrong:

synchronized(myObject) {
doSomethingNifty();
}
That said, Locks may be more useful for more complicated things where you can't acquire and release in such a clean manner. I would honestly prefer to avoid using bare Locks in the first place, and just go with a more sophisticated concurrency control such as a CyclicBarrier or a LinkedBlockingQueue, if they meet your needs.

Anonymous said...

Why does the diagram show three locks (A,B and C) when (unless I'm wrong), the code only contains one - one Counter and one Lock?

grimmeld said...

Your lockdemo code actually gave me duplicates (I copy pasted it without changes, ran it on java 8 32-bit). This was somehow fixed with the following code change (though I'm still trying to figure out why)

public final int getCount() {
lock.lock();
try {
return count++;
} finally {
lock.unlock();
}
}

grimmeld said...

After debugging I found out why there's duplicates: After the unlock but BEFORE the actual return another thread modifies the counter, and so the thread who first modified it returns a value that is modified a second time by the interfering thread. Apparently returning within the finally block cancels this as the value is already returned? Or maybe copied for a return? But either way the value is safely available for the calling thread and only then afterwards is the lock unlocked.

Another option would be to have a local variable and copy the value into it like so:

public final int getCount() {
int counter;
lock.lock();
try {
counter = count++;
} finally {
lock.unlock();
}
return counter;
}

Anonymous said...

Hi, should not the return statement be within try block, to avoid data race between finalization and return?

javin paul said...

Hello Guys, Yes, return statement should be inside try block. Point mentioned by @Grim and @Anonymous are perfectly valid. This is just shows how easy to create subtle bugs with locks :-) That's why, as @Saral suggested, if your need is simple use synchronized keyword. only use locks if you know what you are doing and make sure to review your code from at-least two people.

@Unknown and @Vivek, Thank you very much for your comment and glad that you find this tutorial useful.

Mukesh said...

Unlock after the sleep. You will be good.

Anonymous said...

Hi Javin ,

Very nice article on Locks. What I think is they could have made this as Autocloseable in the later releases. That way it would have been as good as using synchronized. What is your opinion on this?

m said...

locks are scary to deal with

Anonymous said...

thanks for this nice tutorial on Lock class in Java, but you please a provide a similar tutorial to teach how to use Lock with Condition in Java? I have tried myself but condition is very confusing for me, hope your tutorial can help me to learn both Lock and Condition class better.

Minh Quyet said...

How about lock using in multiple JVMs environment. Does this lock work?

Unknown said...

Hi, should not the return statement be within try block, to avoid data race between finalization and return?

Post a Comment