AQS needs to solve the following problems:
1. Lock status. How can I ensure a secure update in the case of concurrency?
2. Where can I keep the lock when the current thread cannot get it? AQS is placed in a queue.
3. How to improve efficiency?
AQS has two core implementation methods: Acquire and:
Public final void acquire (INT Arg ){
If (! Tryacquire (ARG )&&
Acquirequeued (addwaiter (node. Exclusive), ARG ))
Selfinterrupt ();
}
Public final Boolean release (INT Arg ){
If (tryrelease (ARG )){
Node H = head;
If (H! = NULL & H. waitstatus! = 0)
Unparksuccessor (h );
Return true;
}
Return false;
}
AQS calls the acquire and release methods to obtain and release locks.
The responsibilities of the acquire method are as follows:
If the current thread cannot obtain the lock, encapsulate it into a node into the queue and decide when to block the current thread.
Otherwise, return the result directly when the lock is obtained.
Release is responsible:
Try to release the lock (if the current thread does not own the object lock, subsequent operations are not allowed, that is, only the thread with the lock can perform release on the lock ),
If it succeeds, the next node of the header is located in the queue (the blocked thread is stored), and The unpark method of locksupport is called to wake it up.
The tryacquire and tryrelease methods in aquire and release are completed by sub-classes. The sub-classes add some features when obtaining and Releasing locks, such as performing fair locks, non-fair locks, and timeout.
AQS is mainly responsible for putting threads into the clh Queue (a variant) when the lock cannot be obtained ). When a thread releases the current lock, find the next node of the header node (representing a blocking thread) and
Wake it up.
All blocking and wakeup operations here use the locksupport's Park (Object blocker) and unpark (thread) methods.
AQS defines four methods for sub-classes to implement personalized locks if they can be interrupted, as follows:
Protected Boolean tryacquire (INT Arg ){
Throw new unsupportedoperationexception ();
}
Protected Boolean tryrelease (INT Arg ){
Throw new unsupportedoperationexception ();
}
Protected int tryacquireshared (INT Arg ){
Throw new unsupportedoperationexception ();
}
Protected Boolean tryreleaseshared (INT Arg ){
Throw new unsupportedoperationexception ();
}
Reentrantlock uses AQS as the basic framework and uses sync internally. This class inherits AQS.
First, let's look at the process that occurs when the next thread requests a reentrantlock lock, that is, the lock method of reentrantlock is called:
We can see that the thread wants to obtain the lock. During operation 1, the specific acquisition operation here is:
Check whether the current lock state is 0 (indicating that the current lock is not occupied). If not, it indicates that it has been obtained by other threads and enters the can not branch.
If the current lock state is 0, use the CAS operation to change the lock state to 1. After CAS succeeds, change the exclusiveownerthread to the current thread.
The lock code of the sync class of reentrantlock is as follows:
Final void lock () {If (compareandsetstate (0, 1) setexclusiveownerthread (thread. currentthread (); else // call the parent class AQS method acquire (1 );}
If the current thread cannot obtain the lock immediately, call the AQS method of the parent class.
When you enter the acquire method, you will try again to obtain the lock. The tryaquire method is called. This tryaquire method is implemented by the reentrantlock sync class subclass.
Take a look at the acquire method of the parent class AQS. The Code is as follows:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
The thread that obtains the lock will first call the tryacquire method. If tryacquire still cannot obtain the lock, it will encapsulate the current thread into a node and put it into the clh queue.
Here we will first look at how the subclass designs tryacquire. We mainly look at the unfair lock Implementation of reentrantlock. The Code is as follows:
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false;}
We can see that the IF (C = 0) Branch will be taken first. First, we can determine whether the State of the lock object is 0. If so, we can still perform the CAS setting status operation, then, set exclusiveownerthread to the current thread.
If it still cannot be obtained, check whether the current thread is the exclusiveownerthread object on the lock object. The reason for this is that the thread that has obtained the lock is used, if you try to obtain the lock later, let the calling thread know that it can obtain the lock (or that it already has the lock ). Here, we simply add 1 to the number of lock acquires and set it to the state.
This is also the meaning of reentrantlock. That is to say, if a thread a acquires two values for a single lock object, it calls the lock method twice.
(Assuming that thread B is waiting for the lock held by thread A and has been blocked in the clh Queue), thread a can wake up thread B only after it executes the unlock twice in a row.
After learning about the tryacquire (ARG) method in the acquire method of AQS, The tryacquire (ARG) method returns false, that is:
If (! Tryacquire (ARG )&&
Acquirequeued (addwaiter (node. Exclusive), ARG ))
Selfinterrupt ();