| good praise, please, get into the habit of
You have one thought, I have one thought, and when we exchange, one person has two thoughts
If you can NOT explain it simply, you do NOT understand it well enough
Now the Demo code and technical articles are organized together Github practice selection, convenient for everyone to read and view, this article is also included in this, feel good, please also Star🌟
See this period content so little, is not enchanted?
preface
The last article on Java AQS queue synchronizer and ReentrantLock has provided enough preparation for us to read JUC source code and its design ideas. In the following content, I will focus on differentiation. If some children are not able to understand some of the content in the article, I strongly recommend returning to the article. Get the basics right and the rest of the reading will be really relaxing and enjoyable
In AQS, we introduce a variety of scenarios for obtaining synchronous state exclusively:
- Exclusive lock acquisition
- An exclusive fetch lock that responds to interrupts
- Exclusive acquire lock with timeout limit
AQS provides template method there is still no introduction to the shared access synchronization state, so we will unveil this seemingly mysterious veil today
Shared access to synchronization status in AQS
Exclusivity is a form of mutual exclusion, whereas sharing is obviously not, so the only difference between them is:
Whether more than one thread can obtain synchronization state at the same time
In a nutshell, it’s like this:
We know that synchronization state is maintained in THE AQS. Aside from the concept of reentrant locks, AS I mentioned in the previous article, the difference between exclusive and shared control synchronization state is only this:
So if you want to understand the template method of AQS xxxShared, you just need to know how it controls state
AQS shared access to synchronous state source code analysis
In order to help you better recall the content, I will paste two key content of the last article here, to help you quickly recall, about sharing, you just need to pay attention to [SAO purple] can
Custom synchronizer methods need to be overridden
AQS provides the template method
That’s where the story starts (and you’ll notice the striking resemblance to exclusiveness), with key code commented out
public final void acquireShared(int arg) {
If the result is less than zero, it fails to get the synchronization status
if (tryAcquireShared(arg) < 0)
// Call the template method provided by AQS to enter the wait queue
doAcquireShared(arg);
} Copy the code
Enter the doAcquireShared method:
private void doAcquireShared(int arg) {
// Create SHARED node "SHARED" and add it to the queue
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false; // Enter the "spin", which is not a dead loop in the pure sense, as explained in the exclusive form for (;;) { // Also try to get the precursor node of the current node final Node p = node.predecessor(); // If the drive node is the head node, try to get the synchronization status again if (p == head) { // Get synchronization status here in non-blocking mode int r = tryAcquireShared(arg); // If the result is greater than or equal to zero, the outer loop can be returned if (r >= 0) { // Here is the difference between and exclusive setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } Copy the code
In line 18 of the above code, we mentioned the difference between exclusive access to synchronization state, and intimate to give you a more direct comparison:
The difference is only here, so let’s look at what setHeadAndPropagate(node, r) actually does. As I said earlier, the method naming in JDK source code is mostly straightforward. The literal translation of the method is set header and propagate/propagate. Exclusive only set the head, sharing in addition to setting the head also a spread, your question should have come:
What is propagation, and why is there a setting for propagation?
To understand this, you need to know the meaning of non-blocking shared fetch synchronization state return value:
The propagate here is actually in the case of propagate > 0, the reason is very simple, the current thread succeeds in getting the synchronization state, there is still synchronization state available for other threads to get, then the thread waiting in the queue should be informed, let them try to get the remaining synchronization state
If you want the thread on the waiting queue to get a notification, you need to call the release method. Next, we approached Sethead Propagate to check it out and verify it
// Input parameter, node: the current node
Propagate: Gets the result value of synchronization state, that is, the variable r in the method above
private void setHeadAndPropagate(Node node, int propagate) {
// Record the old header node for the check below
Node h = head;
// Set the current node to the head node setHead(node); // Check whether the doReleaseShared method can be called using the propagate value and waitStatus value if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; // Wake up the successor node if it is empty or of the shared type S == null; s == null; s == null if (s == null || s.isShared()) doReleaseShared(); } } Copy the code
We’ve seen the general direction of the method above, but the logic for deciding when to call doReleaseShared in the code is a bit confusing.
The empty judgment here is a bit too big for my head, so let’s pick it out and explain it:
After eliminating the interference of other judgment conditions, we will focus on analyzing the two judgment conditions propagate and waitStatus. Here are several states of waitStatus to help you understand that [SAO pink] is what we will use later:
propagate > 0
As mentioned above, if true, directly short circuit subsequent judgment, and then release according to doReleaseShared judgment conditions
Propagate > 0 is not valid, h.waitStatus < 0 is valid (note that h here is the old head node)
When does H.waitStatus < 0? Regardless of the CONDITION use, there is only SIGNAL and PROPAGATE left. For the answer, you need to take a look at the doReleaseShared() method in advance:
private void doReleaseShared(a) {
for (;;) {
Node h = head;
if(h ! =null&& h ! = tail) { int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // CAS sets the state of the head node to 0 if(! compareAndSetWaitStatus(h, Node.SIGNAL,0)) continue; // loop to recheck cases // The next node of the first node will wake up after the loop is broken unparkSuccessor(h); } else if (ws == 0 && // Set CAS to PROPAGATE state ! compareAndSetWaitStatus(h,0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } } Copy the code
As you can see from the doReleaseShared() method:
-
If h.waitStatus < 0 is set, it can only be set as PROPAGATE = -3, the premise of setting successfully is that the expected state of h head node is 0;
-
If h.waitStatus = 0, CAS is set successfully at line 8 above and the waiting thread is woken up
Therefore, it is speculated that before the current thread executes the judgment of H. waitStatus < 0, another thread just executes the doReleaseShared() method, which sets waitStatus to PROPAGATE = -3 again
This is a little confusing, but let’s draw a picture to understand it:
Propagate <> = 0, propagate = 0 means that the attempt to get the synchronization state is not successful, but then another shared state may be released later, so the logic above is just in case, You know, rigorous concurrency is all about protecting against accidents. Now combine that with the above judgment, does that make sense to you?
And if you keep looking down,
Antecedent condition is set up, (h = head) = = null | | h.w. aitStatus < 0 note here h is the new head node)
With the above matting, it is easier to draw a picture of this, in fact, there is no coincidence that another thread mixed
I believe that by now you should understand the whole process of the shared mode getting the synchronization state, as for the non-blocking mode getting the synchronization state and the synchronization state with timeout, it is really the same according to the propagate logic and the implementation process of the exclusive mode getting the synchronization state, so I don’t have to worry about it any more. Open your IDE and check it out
We have analyzed the AQS template method and haven’t said how the tryAcquireShared(ARG) method was rewritten yet. To understand this, let’s take a look at the classic application Semaphore
Semaphore application and source code analysis
Semaphore concept
“Semaphore” means “Semaphore” and “Semaphore” means “Semaphore”.
Two flags, for example, a traffic light. Each traffic light produces two different behaviors
- Flag1- Red light: Stop
- Flag2- Green: Driving
In Semaphore, when is red and when is green is tryAcquireShared(ARG)
- If the share status cannot be obtained, the red light is displayed
- If the shared status is obtained, the green light is displayed
So let’s go to Semaphore and see how it applies AQS and how it overwrites the tryAcquireShared(ARG) method
Semaphore source code analysis
Let’s look at the class structure
If you are a little surprised by this, the similarity to ReentrantLock is scary. If you are unfamiliar, I strongly recommend you to go back to the article Java AQS queue synchronizer and ReentrantLock application. The tryAcquireShared(ARG) method is not fair
What is the initial value of state? That, of course, is the constructor:
public Semaphore(int permits) {
// Default is still unfair synchronizer. Why default is unfair was specifically explained in the previous article
sync = new NonfairSync(permits);
}
NonfairSync(int permits) { super(permits); } Copy the code
Super method, which gives the initial value to state in AQS
With permits set to 1, a ReentrantLock will have an exclusive with Semaphore. Semaphore will also have an exclusive with Semaphore
static int count;
// Initialize the semaphore
static final Semaphore s
= new Semaphore(1);
// Use semaphores to ensure mutual exclusion static void addOne(a) { s.acquire(); try { count+=1; } finally { s.release(); } } Copy the code
But Semaphore is definitely not for this special case. It is an implementation of shared access to synchronized state. If a semaphore is used, we usually set permits to a value greater than 1. Do you remember that I used to talk about why thread pools are used? The pooling concept described in this article allows multiple threads to use the connection pool at the same time, and no other threads are allowed to use it until each connection is released. So Semaphore can allow multiple threads to access a critical section, ultimately achieving a very good limiting/limiting/limiting effect
Although Semaphore can provide flow limiting function well, to be honest, the flow limiting function of Semaphore is relatively simple. I don’t use Semaphore very much in my actual work. If I really want to use high-performance current limiting device, Guava RateLimiter is a very good choice. We’ll do the analysis later, so if you’re interested, you can check it out in advance
Semaphore source code, so three things in two end
conclusion
I do not know you have the feeling that our rhythm is obviously accelerated, a lot of original scattered points are in crazy series, if in accordance with this way to read JUC source code, I believe you will not be a head into the lost direction, and then frustrated exit JUC, and then recited the answer, and then forget, and then recited?
Semaphore is a classic app for sharing synchronized status. ReadWriteLock and CountDownLatch are widely used on a daily basis. We’ll talk about them later
Soul asking
- Permitting Semaphore set to 1 with a simple muentrantLock implementation, it will be different with ReentrantLock.
- How did you use Semaphore on your project?
reference
- Java Concurrency Combat
- The art of Concurrent programming in Java
- https://blog.csdn.net/anlian523/article/details/106319294