The article was first published in the public number [look at the code to go to work], welcome to the onlookers, the first time to get the latest article.
directory
I met already
What is the AQS
The synchronization status in AQS is state
CLH variant queue
Exclusive mode
Sharing model
Fair locks & Unfair locks
conclusion
The article was first published in the public number [look at the code to go to work], welcome to the onlookers, the first time to get the latest article.
Today, ReentrantLock and AQS will take a look at our Java lock source code.
I met already
First, we enable 5 threads at the same time to accumulate a public variable counter starting at 0, and each thread only increments the variable by 1. Ideally, the public variable ends up equal to 5, similar to the following result:
Thread (0) @, thread state:RUNNABLE thread(1) @, thread state:WAITING thread(2) @, Thread state:WAITING thread(3) @ [WAITING thread(4)], Thread state:WAITING counter:2 thread(0) @ State :TERMINATED thread(1) @ Thread state:RUNNABLE thread(2) @, thread state:WAITING thread(3) @, Thread state:WAITING thread(4) @, thread state:WAITING counter:3 thread(0) @ Thread state:TERMINATED thread(1) @ state:TERMINATED thread(2) @ Thread state:WAITING thread(3) @, thread state:RUNNABLE thread(4) @ Thread state:WAITING counter:4 thread(0) @ State :TERMINATED thread(1) @ Thread state:TERMINATED thread(2) @ state:RUNNABLE thread(3) @ state:TERMINATED thread(2) @ state:RUNNABLE thread(3) @ Thread state:TERMINATED thread(4) @, thread state:WAITING counter:5 thread(0) @, Thread state:TERMINATED thread(1) @ state:TERMINATED thread(2) @ Thread state:TERMINATED thread(3) @ : threadstate :RUNNABLE thread state:TERMINATED thread(4) @ : threadstate :RUNNABLECopy the code
Only one thread is in RUNNABLE state at a time. The rest of the threads are in TERMINATED state or WAITING.
I also sent out a screenshot of the test code:(PS: To test the source code, you can download it here:Github.com/iam-tin/tin…
Most of our initial understanding of ReentrantLock starts with the lock() and unlock() methods, which we use a lot (along with tryLock() and so on).
How does ReentrantLock implement shared variable single-thread locking when we call the lock() method? When unlock() is called, how does ReentrantLock release the lock and notify another WAITING thread to acquire it?
The above questions involve the well-known AQS in Java. ReentrantLock#lock() is actually a call to the lock() method of the abstract inner class Sync. FairSync and NonfairSync inherit from the Sync class, respectively:
To find the Sync class is inherited AbstractQueuedSynchronizer (AQS),Both FairSync and NonFairSync are implemented at the bottom using AQS capabilities.
What is the AQS
When it comes to the concurrent programming in Java, must not open around AQS (AbstractQueuedSynchronizer), it is our great Java author Doug Lea (I really liked the old man) and a great masterpiece!
Take a look at what the official documentation says about AQS:
AQS, also known as abstract queue synchronizer, is the basic framework for packet concurrency (java.util.concurrent). Many familiar locking and synchronization components rely on AQS. Examples include ReentrantLock, ReentrantReadWriteLock, CountDownLatch, and Semaphore. The bottom layer of AQS relies on CAS (contending locks) and synchronous queues, as shown below:
The synchronization status in AQS is state
For different threads to acquire locks, AQS are essentially controlled by a volatile variable, known as state, which represents the synchronization state. There are three main state-related methods: getState(),setState(),compareAndSetState()
/**
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Copy the code
Obviously, threads can acquire locks through the compareAndSetState method (actually CAS). Where is compareAndSetState called? See below:CompareAndSetState (0, 1) indicates that the thread attempts to acquire the lock for the first time. CompareAndSetState (c, c + acquires) indicates that the thread attempts to acquire the lock for the first time.
CLH variant queue
CLH is a bidirectional linked list queue whose key data structure is Node. A Node is a Node in a CLH variant queue that encapsulates threads. A Node can be thought of as a thread preparing to compete for a lock.
Static final class Node {// Static final Node SHARED = new Node(); Static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */ static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3; Volatile int waitStatus; volatile int waitStatus; // Pointer to volatile Node prev; // Volatile Node next; // The Thread bound to the node is volatile Thread Thread; // The next Node to wait for the condition Node nextWaiter; }Copy the code
Exclusive mode
In exclusive mode, which means that only one thread can own the lock at a time (unlike ReentrantLock, which means that the same thread can acquire the lock multiple times), a ReentrantLock is an exclusive lock (which is also reentrant). Let’s look at the logical acquire() method in exclusive mode:
public final void acquire(int arg) { if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code
TryAcquire (ARG) attempts to acquire the lock, wraps the current thread as a node in exclusive mode at the end of the queue if not, and acquireQueued suspends the thread and blocks until the lock is acquired.Acquire () release(int arg)
Sharing model
Shared mode means that the same lock can be owned by multiple threads at the same time. ReadWriteLock, CountdownLatch, and Semaphere are shared modes that we often see.
In contrast to the exclusive acquire() method, let’s look at the acquireShared(int arg) method in shared mode.
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
Copy the code
TryAcquireShared (ARG) is just a template defined by AQS, and is implemented in various lock implementation classes. If the return value of tryAcquireShared(ARG) is greater than or equal to 0, the lock is considered successful. Let’s take a quick look at how the Semaphore’s tryAcquireShared implementation looks:Semaphore also has fair and unfair locks. By default, the locks are unfair. Semaphore is relatively simple. It only recognizes the amount of Semaphore remaining and can acquire the lock if it does not exceed it. Therefore, the same shared resource can be shared by multiple threads at the same time (if the Semaphore is greater than 1).ReadWriteLock is a bit more complicated because it has read and write locks, but it’s easy to understand if you remember that read locks are in shared mode, write locks are in exclusive mode, and different nodes in the CLH queue for the same lock can be in either shared or exclusive mode.
Back to the original method of acquiring a shared lock:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
Copy the code
If tryAcquireShared(ARg) < 0, doAcquireShared(arg) is executed. The main logic of this method is to wrap the thread as a shared mode Node and put it at the end of the synchronized queue until the lock is acquired. However, unlike exclusive mode, doAcquireShared immediately informs subsequent nodes to acquire the lock if it has been acquired.
Fair locks & Unfair locks
Fairness and unfairness are generally implemented by concrete lock implementation classes, and AQS itself does not have this concept. In the case of ReentranLock, the difference between fair and unfair locks internally is whether locks are acquired in a strict queued order:
- If the lock is held by another thread, then the other thread applying for the lock will be suspended and wait, join the end of the waiting queue, and follow the first-in, first-out principle to queue for the lock, which is fair lock.
- An unfair lock allows the thread that is currently requesting to jump the queue first to obtain the lock (regardless of whether there are other threads in the waiting queue waiting for the lock). If it obtains the lock, it will return directly. If it does not obtain the lock, it will join the end of the waiting queue.
ReentranLock exclusive lock source code to understand the difference between fair and unfair lock acquisition:
conclusion
I am Tin, an ordinary engineer who is trying to make himself better. My experience is limited, knowledge is shallow, if you find something wrong with the article, you are very welcome to add me, I will carefully review and modify.
It is not easy to persist in creation. Your positive feedback is the most powerful motivation for me to persist in output. Thank you!
The article was first published in the public number [look at the code to go to work], welcome to the onlookers, the first time to get the latest article.
Attach a link to the original text: mp.weixin.qq.com/s/-yO20_kUh…