ReentrantLock in Java is a part of the java.util.concurrent package that helps to achieve synchronization more effectively and optimally compared to the traditional Synchronized keyword. It offers features like,
- Timeouts
- Interruptible locks
- More control over Thread Scheduling
These features make it a valuable tool for managing concurrent access to shared resources with greater precision and adaptability.
A ReentrantLock allows a thread to acquire the same lock multiple times, which is particularly useful when a thread needs to access a shared resource repeatedly within its execution. It implements the Lock interface, providing greater control over locking compared to synchronized blocks.
- ReentrantLock tracks a "hold count", which:
- Starts at 1 when a thread first locks the resource.
- Each time the thread re-enters the lock, the count is incremented.
- The count is decremented when the lock is released.
- Once the hold count reaches zero, the lock is fully released.
Example 1: Below is a simple Java code demonstrating how to use ReentrantLock to prevent race conditions when accessing a shared counter.
Java
// Java program to demonstrate the use of ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
class Geeks
{
// counter value shared across the threads
private static int c = 0;
// Lock object
private static ReentrantLock lock
= new ReentrantLock();
public static void increment()
{
// acquire the lock
lock.lock();
try
{
c++;
System.out.println(Thread.currentThread().getName()
+ " incremented counter to: " + c);
}
finally
{
// unlock used to
// release the lock
lock.unlock();
}
}
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 1; i < 3 ; i++) {
increment();
}
};
Thread t1 = new Thread(task, "Thread-1");
Thread t2 = new Thread(task, "Thread-2");
t1.start();
t2.start();
}
}
OutputThread-1 incremented counter to: 1
Thread-2 incremented counter to: 2
Thread-2 incremented counter to: 3
Thread-1 incremented counter to: 4
Explanation: In the above example, the shared counter "c" multiple threads increment the counter, but the lock ensures only one thread accesses it at a time, preventing race conditions. The lock is acquired with lock.lock() and released with lock.unlock(). Unlock statement is always called in the finally block to ensure that the lock is released even if an exception is thrown in the method body(try block).
ReentrantLock() Methods
Methods | Description |
---|
lock() | Increments the hold count by 1 and gives the lock to the thread if the shared resource is initially free. |
---|
unlock() | Decrements the hold count by 1. When this count reaches zero, the resource is released. |
---|
tryLock() | If no other thread holds the resource, It returns true and increments the hold count. If busy, it returns false without blocking the thread |
---|
tryLock(long timeout, TimeUnit unit) | The thread waits for a certain period as defined by arguments of the method to acquire the lock on the resource before exiting. |
---|
lockInterruptibly() | Make Thread wait for a resource lock for a set time, interrupting if it times out or if the thread is interrupted. |
---|
getHoldCount() | Returns the count of the number of locks held on the resource. |
---|
isHeldByCurrentThread() | Returns true if the lock on the resource is held by the current thread. |
---|
hasQueuedThread() | Checks if there are any threads waiting to acquire the lock. |
---|
Islocked() | Queries if this lock is held by any thread. |
---|
newCondition() | Returns a Condition instance for use with this Lock instance. |
---|
Example 2: This example demonstrates how to use locks in Java. Below are the steps:
- Create an object of ReentrantLock.
- Create a worker (Runnable object) and pass the lock to it.
- Use the lock() method to acquire the lock on a shared resource.
- After completing work, call the unlock() method to release the lock.
Java
// Java Program to demonstrate Reentrant Locks
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
class Worker implements Runnable {
private ReentrantLock lock;
private String name;
public Worker(ReentrantLock lock, String name) {
this.lock = lock;
this.name = name;
}
@Override
public void run() {
while (!lock.tryLock()) {
System.out.println(name + " waiting for lock");
}
System.out.println(name + " acquired lock");
// Release the lock after completing the task
lock.unlock();
System.out.println(name + " released lock");
}
}
public class Geeks {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
ExecutorService pool = Executors.newFixedThreadPool(2);
// Lock acquiring
pool.execute(new Worker(lock, "Job1"));
pool.execute(new Worker(lock, "Job2"));
// Shutdown the executor service
pool.shutdown();
}
}
OutputJob1 acquired lock
Job1 released lock
Job2 waiting for lock
Job2 acquired lock
Job2 released lock
Explanation: Here, the Worker class attempts to acquire the lock with tryLock(). If the lock is unavailable, it waits and prints a message. Once acquired, it performs its task and releases the lock. The main method sets up a thread pool with two worker instances, allowing them to run concurrently while ensuring thread-safe access to resources.
Note: One can forget to call the unlock() method in the final leading to bugs in the program. Ensure that the lock is released before the thread exits.
Example 3: This example demonstrates how to use ReentrantLock with Condition objects to synchronize even and odd thread operations.
Java
// Java program of ReentrantLock() With Conditions
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class Geeks extends Thread
{
ReentrantLock l = new ReentrantLock();
// Conditions for even/odd
Condition ev = l.newCondition();
Condition od = l.newCondition();
// Counter variable
int c;
Geeks(int c) { this.c = c; }
int MAX = 10;
public void run()
{
while (c <= MAX) {
// Acquire the lock
l.lock();
try {
// Conditions
if (c % 2 == 1
&& Thread.currentThread()
.getName()
.equals("even"))
{
ev.await();
}
else if (c % 2 == 0
&& Thread.currentThread()
.getName()
.equals("odd"))
{
// Wait for odd condition
od.await();
}
else
{
System.out.println(
Thread.currentThread().getName()
+ " Thread " + c);
c += 1;
if (c % 2 == 0)
ev.signal();
else
od.signal();
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
// Release the lock
l.unlock();
}
}
}
public static void main(String[] args)
{
Geeks obj = new Geeks(0);
Thread even = new Thread(obj, "even");
Thread odd = new Thread(obj, "odd");
even.start();
odd.start();
}
}
Outputeven Thread 0
odd Thread 1
even Thread 2
odd Thread 3
even Thread 4
odd Thread 5
even Thread 6
odd Thread 7
even Thread 8
odd Thread 9
even Thread 10
Explanation:
- In the above example, the even threads utilize an even Condition object to wait using the await() method and odd threads use an odd condition to wait using the await() method.
- When the shared resource is even, we release the even thread using the signal() method because the last printed value is an odd value generated by the odd thread.
- When the shared resource(t) is odd, release the odd thread using the signal() method because the last printed value is an even value produced by the even thread.
Important Points:
- A thread waiting on a condition (e.g., evenCondition.await()) should not signal using the same condition (evenCondition.signal()).
- The await() and signal() methods in Condition behave similarly to wait() and notify() in synchronized blocks but provide finer-grained control.
Similar Reads
Multithreading in Java Multithreading is a Java feature that enables the concurrent execution of two or more parts of a program, maximizing CPU utilization. Each part of such a program is called a thread. So, threads are lightweight processes within a process.Example: Imagine a restaurant kitchen where multiple chefs are
4 min read
Lifecycle and States of a Thread in Java A thread in Java can exist in any one of the following states at any given time. A thread lies only in one of the shown states at any instant:New StateRunnable StateBlocked StateWaiting StateTimed Waiting StateTerminated StateThe diagram below represents various states of a thread at any instant:Lif
5 min read
Main thread in Java Java provides built-in support for multithreaded programming. A multi-threaded program contains two or more parts that can run concurrently. Each part of such a program is called a thread, and each thread defines a separate path of execution.When a Java program starts up, one thread begins running i
4 min read
Java Concurrency - yield(), sleep() and join() Methods In this article, we will learn what is yield(), join(), and sleep() methods in Java and what is the basic difference between these three. First, we will see the basic introduction of all these three methods, and then we compare these three. We can prevent the execution of a thread by using one of th
5 min read
Inter-thread Communication in Java Inter-thread communication in Java is a mechanism in which a thread is paused from running in its critical section, and another thread is allowed to enter (or lock) the same critical section to be executed.Note: Inter-thread communication is also known as Cooperation in Java.What is Polling, and Wha
6 min read
Java Thread Class Thread is a line of execution within a program. Each program can have multiple associated threads. Each thread has a priority which is used by the thread scheduler to determine which thread must run first. Java provides a thread class that has various method calls to manage the behavior of threads b
5 min read
What does start() function do in multithreading in Java? We have discussed that Java threads are typically created using one of the two methods : (1) Extending thread class. (2) Implementing RunnableIn both the approaches, we override the run() function, but we start a thread by calling the start() function. So why don't we directly call the overridden ru
2 min read
Java Thread Priority in Multithreading Java being Object-Oriented works within a Multithreading environment in which the thread scheduler assigns the processor to a thread based on the priority of the thread. Whenever we create a thread in Java, it always has some priority assigned to it. Priority can either be given by JVM while creatin
5 min read
Joining Threads in Java java.lang.Thread class provides the join() method which allows one thread to wait until another thread completes its execution. If t is a Thread object whose thread is currently executing, then t.join() will make sure that t is terminated before the next instruction is executed by the program. If th
3 min read
Java Naming a Thread and Fetching Name of Current Thread A thread can be referred to as a lightweight process. Assigning descriptive names to threads enhances code readability and simplifies debugging. Now let us discuss the different ways to name a thread in Java.Methods to Set the Thread NameThere are two ways by which we can set the name either be it d
4 min read