[Java - Synchronization] Locks
Simply put, a lock is a more flexible and sophisticated thread synchronization mechanism than the standard synchronized block.
The Lock interface has:
void unlock() – unlocks the Lock instance
void lockInterruptibly() – this is similar to the lock(), but triggering this will also throw a InterruptedException. This allows the blocked thread to be interrupted and resume the execution through a thrown java.lang.InterruptedException (when is this really used practically?)
boolean tryLock() – this is a non-blocking version of lock() method; it attempts to acquire the lock immediately, return true if locking succeeds
boolean tryLock(long timeout, TimeUnit timeUnit) – this is similar to tryLock(), except it waits up the given timeout before giving up trying to acquire the Lock
getHoldCount(): This method returns the count of the number of locks held on the resource.
isHeldByCurrentThread(): This method returns true if the lock on the resource is held by the current thread
Another feature provided by StampedLock is optimistic locking. Most of the time read operations doesn’t need to wait for write operation completion and as a result of this, the full-fledged read lock isn’t required.
https://www.javacodegeeks.com/2013/11/what-are-reentrant-locks.html
https://www.baeldung.com/java-concurrent-locks
The Lock interface has:
- Been around since Java 1.5
- Defined inside the java.util.concurrent.lock package
0) Locks vs Synchronized Block
- A synchronized block is fully contained within a method, but Locks can have lock() and unlock() operation in separate methods.
- A synchronized block doesn’t support the fairness, any thread can acquire the lock once released, no preference can be specified. We can achieve fairness in lock to ensure the longest waiting thread is given access to the lock
- A thread gets blocked if it can’t get an access to the synchronized block. The Lock API provides tryLock() method. The thread acquires lock only if it’s available and not held by any other thread. This reduces blocking time of thread waiting for the lock
- A thread which is in “waiting” state to acquire the access to synchronized block, can’t be interrupted. The Lock API provides a method lockInterruptibly() which can be used to interrupt the thread when it’s waiting for the lock.
- Can get list of threads waiting for a given lock.
1) Lock Interface
void lock() – acquire the lock if it’s available; if the lock isn’t available a thread gets blocked until the lock is releasedvoid unlock() – unlocks the Lock instance
void lockInterruptibly() – this is similar to the lock(), but triggering this will also throw a InterruptedException. This allows the blocked thread to be interrupted and resume the execution through a thrown java.lang.InterruptedException (when is this really used practically?)
boolean tryLock() – this is a non-blocking version of lock() method; it attempts to acquire the lock immediately, return true if locking succeeds
boolean tryLock(long timeout, TimeUnit timeUnit) – this is similar to tryLock(), except it waits up the given timeout before giving up trying to acquire the Lock
getHoldCount(): This method returns the count of the number of locks held on the resource.
isHeldByCurrentThread(): This method returns true if the lock on the resource is held by the current thread
1.0) Example Code:
import java.util.concurrent.locks.ReentrantLock;
public class MyRentrantlock {
Thread t = new Thread() {
@Override
public void run() {
ReentrantLock r = new ReentrantLock();
r.lock();
System.out.println("lock() : lock count :" + r.getHoldCount());
interrupt();
System.out.println("Current thread is intrupted");
r.tryLock();
System.out.println("tryLock() on intrupted thread lock count :" + r.getHoldCount());
try {
r.lockInterruptibly();
System.out.println("lockInterruptibly() -- Not executable statement because InterruptedException Thrown " + r.getHoldCount());
} catch (InterruptedException e) {
r.lock();
System.out.println("Error");
} finally {
r.unlock();
}
System.out.println("lockInterruptibly() not able to Acqurie lock: lock count :" + r.getHoldCount());
r.unlock();
System.out.println("lock count :" + r.getHoldCount());
r.unlock();
System.out.println("lock count :" + r.getHoldCount());
}
};
lock() : lock count :1
Current thread is intrupted
tryLock() on intrupted thread lock count :2
Error
lockInterruptibly() not able to Acqurie lock: lock count :2
lock count :1
lock count :0
lock() : lock count :1
Current thread is intrupted
tryLock() on intrupted thread lock count :2
Error
lockInterruptibly() not able to Acqurie lock: lock count :2
lock count :1
lock count :0
1.1) Implementation Best Practices
A locked instance should always be unlocked to avoid deadlock condition. A best practice to use the lock is to contain it in a try/catch and finally block:
In addition to the Lock interface, we have a ReadWriteLock interface which maintains a pair of locks, one for read-only operations, and one for the write operation. The read lock may be simultaneously held by multiple threads as long as there is no write.
ReadWriteLock declares methods to acquire read or write locks:
Lock readLock() – returns the lock that’s used for reading
Lock writeLock() – returns the lock that’s used for writing
In this case, the thread calling tryLock(), will wait for one second and will give up waiting if the lock isn’t available.
Read Lock – if no thread acquired the write lock or requested for it then multiple threads can acquire the read lock
Write Lock – if no threads are reading or writing then only one thread can acquire the write lock
StampedLock is introduced in Java 8. It also supports both read and write locks. However, lock acquisition methods returns a stamp that is used to release a lock or to check if the lock is still valid.
1
2
3
4
5
6
7
| Lock lock = ...; lock.lock(); try { // access to the shared resource } finally { lock.unlock(); } |
ReadWriteLock declares methods to acquire read or write locks:
Lock readLock() – returns the lock that’s used for reading
Lock writeLock() – returns the lock that’s used for writing
2) Lock Implementation
2.1) Reentrant Lock
The ReentrantLock class implements the Lock interface and provides synchronization to methods while accessing shared resources.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public class SharedObject { //... ReentrantLock lock = new ReentrantLock(); int counter = 0 ; public void perform() { lock.lock(); try { // Critical section here count++; } finally { lock.unlock(); } } //... } |
2.1.1) Fairness Policy
The ReentrantLock constructor offers a choice of two fairness options :nonfair or a fair lock.
With fair locking threads can acquire locks only in the order in which they requested, whereas an unfair lock allows a lock to acquire it out of its turn, this is called barging (breaking the queue and acquiring the lock when it became available).
Fair locking has a significant performance cost because of overhead of suspending and resuming threads. There could be cases where there is a significant delay between when a suspended thread is resumed and when it actually runs.
Fair locking has a significant performance cost because of overhead of suspending and resuming threads. There could be cases where there is a significant delay between when a suspended thread is resumed and when it actually runs.
Implementation:
new ReentrantLock(boolean fair)
2.1.2) tryLock
Let’s see how the tryLock() works:
1
2
3
4
5
6
7
8
9
10
11
12
13
| public void performTryLock(){ //... boolean isLockAcquired = lock.tryLock( 1 , TimeUnit.SECONDS); if (isLockAcquired) { try { //Critical section here } finally { lock.unlock(); } } //... } |
2.2) Reentrant ReadWriteLock
ReentrantReadWriteLock class implements the ReadWriteLock interface.Read Lock – if no thread acquired the write lock or requested for it then multiple threads can acquire the read lock
Write Lock – if no threads are reading or writing then only one thread can acquire the write lock
2.3) StampedLock
Another feature provided by StampedLock is optimistic locking. Most of the time read operations doesn’t need to wait for write operation completion and as a result of this, the full-fledged read lock isn’t required.
3) Working with Conditions
A java.util.concurrent.locks.Condition interface provides a thread ability to suspend its execution, until the given condition is true. A Condition object is necessarily bound to a Lock and to be obtained using the newCondition() method.
3.1) Methods
Conditions have similar methods as locks (but with different names).
Lock Method | Corresponding Condition Method |
---|---|
wait |
public void await()
Causes the current thread to wait until it is signalled or interrupted.
|
notify |
public void signal()
Wakes up one waiting thread.
|
notifyAll |
public void signalAll()
Wakes up all waiting threads.
|
3.2) Example
3.2.1) Output
Pushing the item Item 0
Pushing the item Item 1
pool-1-thread-1 wait on stack full
Item popped Item 1
Pushing the item Item 2
pool-1-thread-1 wait on stack full
Item popped Item 2
Pushing the item Item 3
pool-1-thread-1 wait on stack full
Item popped Item 3
Pushing the item Item 4
Item popped Item 4
Item popped Item 0
3.2.1) Explanation
Even though in the output log, the first two items are pushed to the stack, after line 28, the lock is actually released and the pushThread and popThread competes for the lock. (Will explain my observations below).
When a condition is met, the condition is set to wait (stackEmptyCondition.await() or stackFullCondition.await()) until it's notified to continue.
Then the push and pop thread alternates until one of them has completed all iterations.
3.2.2) Observations
I have a small confusion about the competing lock part. My observation is as follows:
After I increase the CAPACITY to 15, it seems that the pushThread is consistently able to compete the lock 15 times in a row until it reaches the upper limit.
However, when I add a Thread.sleep(1) at line 29, I do see the popThread able to win the lock competition back (as shown in log below) - proving that the lock is indeed unlocked and unblocked by something else.
Pushing the item Item 0
Item popped Item 0
pool-1-thread-2 wait on stack empty
Pushing the item Item 1
...
Resources
https://www.geeksforgeeks.org/reentrant-lock-java/https://www.javacodegeeks.com/2013/11/what-are-reentrant-locks.html
https://www.baeldung.com/java-concurrent-locks
Comments
Post a Comment