In the previous blog, we learned the process of acquiring locks of fair locks. Now, let’s take a look at the process of releasing locks of fair locks.
Release fair lock (based on JDK 11.0.5)
1. unlock()
unlock() is implemented in ReentrantLock
1 | public void unlock() { |
unlock()
is the unlock function, which is implemented through AQS
‘s release()
function.
Here, the meaning of 1
is the same as the meaning of the function acquire(1)
that acquires the lock, which is the parameter that sets the state of releasing the lock. Since the fair lock is reentrant, for the same thread, the state of the lock is -1
every time the lock is released.
The relationship between AQS
, ReentrantLock
and sync
is as follows:
1 | public class ReentrantLock implements Lock, java.io.Serializable { |
sync
is a member object in ReentrantLock
, and Sync
is a subclass of AQS
.
2. release()
release()
is implemented in AQS
1 | public final boolean release(int arg) { |
release()
will first call tryRelease()
to try to release the lock held by the current thread. If successful, wake up the subsequent waiting thread and return true
. Otherwise, directly return false
.
3. tryRelease()
tryRelease()
is implemented in the Sync
class of ReentrantLock
1 | protected final boolean tryRelease(int releases) { |
The purpose of tryRelease()
is to try to release the lock.
- If current thread is not lock holder, then throw an exception.
- If the current thread owns the lock after the current lock release operation is 0 (that is, the current thread completely releases the lock), then the holder of the lock is set to
null
. So the lock Is available. At the same time, update the lock status of the current thread to0
.
getState()
, setState()
have been introduced in the previous blog so we will skip them here.
getExclusiveOwnerThread()
, setExclusiveOwnerThread()
is defined in AbstractOwnableSynchronizer
, the parent class of AQS
1 | public abstract class AbstractOwnableSynchronizer implements java.io.Serializable { |
4. unparkSuccessor()
If the current thread releases the lock successfully in release()
, it will wake up the successor thread of the current thread.
According to the FIFO
rules of the CLH
queue, the current thread (that is, the thread that has acquired the lock) must be the head
. If the CLH
queue is not empty, the next waiting thread of the lock is awakened.
Let’s take a look at the source code of unparkSuccessor()
, which is implemented in AQS
.
1 | private void unparkSuccessor(Node node) { |
unparkSuccessor()
is to wake up the successor thread of the current thread. After the subsequent thread is woken up, it can acquire the lock and resume running.
Summary
The process of releasing the lock is relatively simple compared to the process of acquiring the lock. When releasing the lock, the main operation is to update the state of the lock corresponding to the current thread. If the current thread has completely released the lock, set the lock holding thread to null
, set the current thread’s state to empty, and then wake up the subsequent thread.