In this tutorial, we will see what is a Lock interface in Java, its implementation classes, and methods along with examples.
Table of Contents
Java lock interface
Lock interface is a synchronization technique that is available from JDK 1.5. It is similar to a synchronized block but is more flexible and complicated. The Lock interface is part of the java.util.concurrent
package. It contains 2 main methods lock()
and unlock()
. Wherever we want to ensure that only one thread at a time can access the code, we can acquire the lock by using the lock()
method. Once the process is complete, we can release the lock using the unlock()
method.
You might be interested in Java Interface
Lock vs Synchronized block
Though a lock and synchronized block are similar, there are few differences as below.
Lock | Synchronized block |
---|---|
Lock interface contains Lock API lock() and unlock() that we can use in different methods | Synchronized block is present within a method always |
Lock supports fairness by specifying the fairness property | Does not support fairness |
Locks contain tryLock() method that checks if it can acquire the lock() | Thread is blocked if it does not have access to synchronized block |
Lock contain lockInterrupibilty() method to interrupt a waiting thread | We cannot interrupt a thread that is in waiting state to access the synchronized block |
Java Lock interface methods
Below are the methods present in the Lock interface.
Method | Description |
---|---|
void lock() | Acquires the lock |
void lockInterruptibly() | Acquires the lock until the current thread is interrupted |
Condition newCondition() | Returns a new condition that is bound to the current lock instance |
boolean tryLock() | Acquires the lock only if it is free at the specified time |
boolean tryLock(long time, TimeUnit unit) | Acquires the lock only if it is free at the specified timeperiod until it is not interrupted |
void unlock() | Releases the lock |
Lock implementations
JDK supports multiple implementations for the Lock interface in Java as below:
ReentrantLock
The ReentrantLock class has reentrant characteristics that implement the Lock interface in Java. It acquires a lock using the lock()
method and releases the same using the unlock()
method. It is important to use the unlock()
method within the try-catch
block to avoid deadlock conditions. This lock is thread-safe and ensures that only one thread can access the synchronized code at a time.
ReentrantLock l = new ReentrantLock(); int cnt = 0; void counter() { l.lock(); try { cnt++; } finally { l.unlock(); } }
ReentrantReadWriteLock
The ReentrantReadWriteLock is another class that implements the Lock interface in Java. It has a pair of locks for read and write access. This type of lock ensures the safety to read mutable variables as long as no one writes them simultaneously. This means multiple threads can hold the read lock access on the same object provided there is no write access on it. This improves the performance in situations where a read operation is more than a write operation.
It is important to follow the below rule:
ReadLock: Multiple threads can hold read access until there is no thread holding or requesting a write access
WriteLock: Allows only a single thread to hold write access when there is no read/write access on the same object.
The below is a piece of code that demonstrates the working of ReentrantReadWriteLock.
ExecutorService exec = Executors.newFixedThreadPool(2); Map<Integer, String> m = new HashMap<Integer,String>(); ReadWriteLock rwl = new ReentrantReadWriteLock(); exec.submit(() -> { rwl.writeLock().lock(); try { Thread.sleep(1000); m.put(1,"Java"); } catch (InterruptedException e) { e.printStackTrace(); } finally { rwl.writeLock().unlock(); } }); Runnable read = () -> { rwl.readLock().lock(); try { System.out.println(m.get(1)); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { rwl.readLock().unlock(); } }; exec.submit(read); exec.submit(read);
StampedLock
StampedLock is similar to ReentrantReadWriteLock except that the ReadLocks and WriteLocks can contain a timestamp of long type. We can pass this timestamp while using the unlock() method to release the lock. The StampedLock is available from Java 8 onwards. We can also use the timestamp to check if a lock is valid or not.
Java Lock interface example
Below is a simple example that illustrates the working of the Java Lock interface methods lock()
and unlock()
with multiple threads. Here, we create 3 threads that individually invoke the incCounter()
method that increments the counter. We can see that though all 3 threads have started, it executes only one at a time since we have used the Lock interface. When it calls the lock()
method, the current thread acquires the lock and increments the counter value and releases it by using the unlock()
method. This way it prevents deadlock.
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockDemo { public static void main(String[] args) { ExecuteTask e = new ExecuteTask(); Thread t[] = new Thread[3]; for(int i=0;i<3;i++) { t[i] = new Thread(new CounterDemo(e), "Thread " + i); } for(int i=0;i<3;i++) { t[i].start(); } } } class CounterDemo implements Runnable { ExecuteTask exec; public CounterDemo(ExecuteTask exec){ this.exec = exec; } public void run() { System.out.println("Call executeTask to increment counter: " + Thread.currentThread().getName()); exec.incCounter(); } } class ExecuteTask { int cnt = 0; Lock l = new ReentrantLock(); void incCounter() { l.lock(); try { cnt++; } finally { System.out.println("Executing " + Thread.currentThread().getName() + ":" + " Counter value: " + cnt); l.unlock(); } } }
Call executeTask to increment counter: Thread 1 Call executeTask to increment counter: Thread 0 Call executeTask to increment counter: Thread 2 Executing Thread 1: Counter value: 1 Executing Thread 0: Counter value: 2 Executing Thread 2: Counter value: 3
You might be interested in Multithreading Interview Questions