| 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

  1. Permitting Semaphore set to 1 with a simple muentrantLock implementation, it will be different with ReentrantLock.
  2. How did you use Semaphore on your project?

reference

  1. Java Concurrency Combat
  2. The art of Concurrent programming in Java
  3. https://blog.csdn.net/anlian523/article/details/106319294