Preface
After reading JUC Under bag AQS After the source , There are a lot of questions , The biggest question is state What exactly does it mean ? also AQS It mainly defines the in and out of the queue , But access to resources 、 Releasing resources is all handed over to subclass implementations , How is the subclass implemented ? Let's start to understand ReentrantLock.
official account :『 Liu Zhihang 』, Record the skills in work study 、 Development and source notes ; From time to time to share some of the life experience . You are welcome to guide !
Introduce
A mutex and implicit monitor lock that can be re entered synchronized Have the same basic behavior and semantics , But it's more powerful .
Has the following characteristics :
- Mutual exclusivity : At the same time, only one thread can get the lock , At this point, other threads request the lock , Will be blocked , And then put it inside the lock for maintenance AQS Blocking the queue .
- Reentrancy : maintain state Variable , For the initial 0, When a thread gets a lock ,state Use cas Updated to 1, This thread applies to acquire the lock again , Would be right state Conduct CAS Increasing , The number of repetitions is state, At most 2147483647 . Trying to exceed this limit will be thrown from the lock method Error.
- fair / Unfairness : At initialization , It's possible to transmit parameters through constructors , Specifies whether it is a fair lock , Or unfair lock . When set to true when , Lock for fairness , When threads compete for locks , It tends to wait for the longest thread .
Basic use
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
Question question ?
First of all, when reading this article , Yes AQS With a certain understanding , If you don't understand , Take a look at the previous article . Graphic explanation AQS
- stay AQS Described in the state when , say state The meaning is defined by subclasses , That's in ReentrantLock in state What is the ?
- ReentrantLock and AQS What does it matter ?
- How the thread gets the lock ?
- How the reentrancy of locks is realized ?
- Current thread failed to acquire lock , What are the blocked subsequent operations ?
- How fair lock and unfair lock are embodied ?
- How the lock is released ?
Will be through the source code and drawing the way , Around the above questions , Read and analyze .
Source code analysis
The basic structure
The basic structure is shown in the figure ,ReentrantLock Class implements the interface Lock, At the interface Lock The method of using lock is defined in , The method and meaning are as follows :
public interface Lock {
// Get the lock , If not , It will block .
void lock();
// Get the lock , If not , It will block . In response to interrupt .
void lockInterruptibly() throws InterruptedException;
// Attempt to acquire lock , If you get , return true, No access to return false
boolean tryLock();
// Attempt to acquire lock , There was no access to , Will wait for a specified time , In response to interrupt .
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// Release the lock
void unlock();
}
and ReentrantLock It's just realized Lock Interface , And implement these methods , that ReentrantLock and AQS What does it matter ? This needs to see how the internal implementation is achieved .
As can be seen from the class diagram above , stay ReentrantLock There are two inner classes in , Namely NonfairSync FairSync And they both came true abstract class Sync, abstract class Sync Inherited AbstractQueuedSynchronizer namely AQS. The specific code is as follows :
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
// Lock synchronization control basic class . Subclasses are specific to fair and unfair versions . Use AQS State to indicate the number of locks held .
abstract static class Sync extends AbstractQueuedSynchronizer {
// Omit ...
}
static final class NonfairSync extends Sync {
// Unfair lock logic Omit ...
}
static final class FairSync extends Sync {
// Fair lock logic Omit ...
}
// Default unfair lock
public ReentrantLock() {
sync = new NonfairSync();
}
// Specify fair or unfair lock according to the reference ,true Fair lock ,false Not fair lock
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
As you can see from the code above :
- The basic control of the lock is by NonfairSync and FairSync controlled , And their parents Sync Inherited AQS (AbstractQueuedSynchronizer), That is to say ReentrantLock Implementation and AQS It's about .
- NonfairSync Represents unfair lock implementation logic ,FairSync Represents fair lock implementation logic .
- It can be seen from the constructor that , On initialization , The default is NonfairSync Not fair lock . You can also specify declared fair or unfair locks , The ginseng true by Fair lock ,false Lock for unfairness .
Specifically ReentrantLock and AQS What is the relationship between , It needs to be analyzed by the locking process .
lock
As shown in the figure , Default declaration unfair lock ,lock Method internal invocation sync.lock();
This should be used in the unfair lock inside lock Lock operation .
final void lock() {
// adopt CAS Set up state value 0 -> 1
if (compareAndSetState(0, 1))
// Setup succeeded. The current thread got the lock
setExclusiveOwnerThread(Thread.currentThread());
else
// Setup failed , Call AQS Methods , Attempt to acquire lock .
acquire(1);
}
- First of all be Use CAS to update state Value , At this point, you will find that , state The state of the lock represented here . 0 Unlocked ,1 Lock .
- Setup failed , Would call AQS Of acquire(1); Method .
Let's take a look at AQS Of acquire Code :
public final void acquire(int arg) {
// tryAcquire Try to get state, If the acquisition fails, it will be added to the queue
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Analyze before AQS Source code , It has been introduced tryAcquire It's trying to get state Value ,AQS There are no available methods in , This is implemented by subclasses . So this code is still in NonfairSync Class to implement their own business logic .
static final class NonfairSync extends Sync {
// NonfairSync Realization
protected final boolean tryAcquire(int acquires) {
// Calls a method of the parent class
return nonfairTryAcquire(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
// NonfairSync Parent class of Sync There is realization in
// state Biography is 1
final boolean nonfairTryAcquire(int acquires) {
// Get the current thread
final Thread current = Thread.currentThread();
// obtain state
int c = getState();
// If c yes 0
if (c == 0) {
// Use cas Updated to 1
if (compareAndSetState(0, acquires)) {
// Set the holding thread to current
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// If the current thread holds
// Yes state Add up
int nextc = c + acquires;
// It is not allowed to exceed int The maximum of 2147483647 + 1 = -2147483648
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// Set up state Value
setState(nextc);
return true;
}
return false;
}
}
- The current thread is locked , Use it directly CAS Way to state from 0 Updated to 1, The update is successful , Then get the lock , Update failed , Get failed .
- When the update fails, call AQS Of
acquire(1);
Method , The reference here is 1. tryAcquire Try to acquire the lock again .
- state yes 0, Try to get . Return on success true;
- state No 0, Determine whether the current thread holds , If it is held by the current thread, it will be used for state Add up .
- tryAcquire Lock acquisition failed , Then go AQS Of acquireQueued Logic , Create nodes , And join the waiting queue .
The flow chart is as follows :
- Starting with a single thread
- At this point, other threads come to request the lock
- Lock flow chart
How is fair lock embodied
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// Judge whether there is a node queuing
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
Pull out the code and compare :
You can see in the fair lock (FairSync) There is one more judgment condition in
!hasQueuedPredecessors()
hasQueuedPredecessors Method in AQS in , If there is a thread in front of the current thread queuing to return true, If the current thread is empty in the queue's head or queue , return false.
The code is as follows :
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
If there are nodes queuing up at the time of lock up , Then go to the end of the node , Or else they're going to grab the lock .
Basically, we know the difference between fair lock and unfair lock :
Not fair lock : Whether there are nodes in the queue or not , Will try to get the lock , If the fetch fails , Get into acquire Method , Or try to get it once , And then they're in the queue .
Fair lock : There are already nodes in the queue , Then go to line up behind the node .
tryLock
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
Directly called Sync Medium nonfairTryAcquire, Attempt to acquire lock , Acquisition failure , Just go back to false, Getting a lock or holding a lock by the current thread is true to state After accumulating, they all return to true.
unlock
public void unlock() {
sync.release(1);
}
Find out unlock Directly called AQS Of release Method , To release resources .
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
This one is in AQS In the introduction , Also explain tryRelease Implemented by subclass , Now in ReentrantLock Focus on tryRelease The implementation of the .
// Release resources , The incoming value is 1
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
- Get current state Conduct -1 operation ;
- Determines whether the current thread is the holding thread ;
- If after the release state by 0 , Set the holding thread to null;
- Update and return state Value .
summary
Through the above source code and drawing , Basically, there is an answer to the starting question :
Q: stay AQS Described in the state when , say state The meaning is defined by subclasses , That's in ReentrantLock in state What is the ?
A: stay ReentrantLock in state It stands for lock state ,0 No thread gets lock , Greater than or equal to 1 A thread has already acquired a lock , Greater than 1 Indicates that the thread that obtained the lock has re entered multiple times .
Q:ReentrantLock and AQS What does it matter ?
A:ReentrantLock Internally based on AQS Realization , Whether it's a lock state , Or into the waiting line , Lock release is based on AQS Realization .ReentrantLock Fair lock and unfair lock are both NonfairSync、FairSync To achieve , And their father Sync Inherited AQS.
Q: How the thread gets the lock ?
A: Thread by modifying state Field to get the lock .
Q: How the reentrancy of locks is realized ?
A: The current thread found state No 0 , A lock has been obtained , This will determine whether the thread currently getting the lock is itself , If it is , On the other hand state Add up .
Q: Current thread failed to acquire lock , What are the blocked subsequent operations ?
A: Acquisition failure , Will be in AQS Waiting in the queue , Loop through the queue , Monitor whether the previous node is head , If yes , Will try to acquire the lock again .
Q: How fair lock and unfair lock are embodied ?
A: Fair lock is mainly reflected in if there are already queued threads in the current queue , They are directly behind . Unfair locks are no thread queuing regardless of the current queue , Will try to modify it directly state Get the lock .
Q: How the lock is released ?
A: Lock releases resources , the state Conduct -1 operation , If -1 after state by 0, Release the node , Subsequent nodes try to acquire the lock . Here you can see AQS Related logic .